%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1997-2012 The University of Melbourne.
% Copyright (C) 2015 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: trace_gen.m.
% Author: zs.
%
% This module handles the generation of traces for the trace analysis system.
%
% For the general basis of trace analysis systems, see the paper
% "Opium: An extendable trace analyser for Prolog" by Mireille Ducasse,
% available from http://www.irisa.fr/lande/ducasse.
%
% We reserve some slots in the stack frame of the traced procedure.
% One contains the call sequence number, which is set in the procedure prologue
% by incrementing a global counter. Another contains the call depth, which
% is also set by incrementing a global variable containing the depth of the
% caller. The caller sets this global variable from its own saved depth
% just before the call. We also save the event number, and sometimes also
% the redo layout and the from_full flag.
%
% Each event has a label associated with it. The stack layout for that label
% records what variables are live and where they are at the time of the event.
% These labels are generated by the same predicate that generates the code
% for the event, and are initially not used for anything else.
% However, some of these labels may be fallen into from other places,
% and thus optimization may redirect references from labels to one of these
% labels. This cannot happen in the opposite direction, due to the reference
% to each event's label from the event's foreign_proc_code instruction.
% (This prevents labelopt from removing the label.)
%
% We classify events into three kinds: external events (call, exit, fail),
% internal events (switch, disj, ite_then, ite_else), and nondet foreign_proc
% events (first, later). Code_gen.m, which calls this module to generate
% all external events, checks whether tracing is required before calling us;
% the predicates handing internal and nondet foreign_proc events must check
% this themselves. The predicates generating internal events need the goal
% following the event as a parameter. For the first and later arms of
% nondet foreign_proc, there is no such hlds_goal, which is why these events
% need a bit of special treatment.
%
%-----------------------------------------------------------------------------%

:- module ll_backend.trace_gen.
:- interface.

:- import_module hlds.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.trace_params.
:- import_module ll_backend.code_info.
:- import_module ll_backend.code_loc_dep.
:- import_module ll_backend.continuation_info.
:- import_module ll_backend.llds.
:- import_module mdbcomp.
:- import_module mdbcomp.goal_path.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.set_of_var.

:- import_module assoc_list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module set.

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

    % The kinds of external ports for which the code we generate will
    % call MR_trace. The redo port is not on this list, because for that
    % port the code that calls MR_trace is not in compiler-generated code,
    % but in the runtime system.  Likewise for the exception port.
    % (The same comment applies to the type `trace_port' in llds.m.)
    %
:- type external_trace_port
    --->    external_port_call
    ;       external_port_exit
    ;       external_port_fail
    ;       external_port_tailrec_call.

    % These ports are different from other internal ports (even neg_enter)
    % because their goal path identifies not the goal we are about to enter
    % but the goal we have just left.
    %
:- type negation_end_port
    --->    neg_success
    ;       neg_failure.

:- type trace_info.

:- pred get_trace_maybe_tail_rec_info(trace_info::in,
    maybe(pair(lval, label))::out) is det.

:- type trace_slot_info
    --->    trace_slot_info(
                % If the procedure is shallow traced, this will be yes(N),
                % where stack slot N is the slot that holds the value of the
                % from-full flag at call. Otherwise, it will be no.
                slot_from_full      :: maybe(int),

                % If the procedure has io state arguments this will be yes(N),
                % where stack slot N is the slot that holds the saved value
                % of the io sequence number. Otherwise, it will be no.
                slot_io             :: maybe(int),

                % If --use-trail is set, this will be yes(M), where stack slots
                % M and M+1 are the slots that hold the saved values of the
                % trail pointer and the ticket counter respectively at the time
                % of the call. Otherwise, it will be no.
                slot_trail          :: maybe(int),

                % If the procedure lives on the det stack but creates
                % temporary frames on the nondet stack, this will be yes(M),
                % where stack slot M is reserved to hold the value of maxfr
                % at the time of the call. Otherwise, it will be no.
                slot_maxfr          :: maybe(int),

                % If the procedure's evaluation method is memo, loopcheck
                % or minimal model, this will be yes(M), where stack slot
                % M holds the variable that represents the tip of the
                % call table. Otherwise, it will be no.
                slot_call_table     :: maybe(int),

                % If the procedure has tail recursive call events, this
                % will be yes(M), where stack slot M holds the variable
                % that represents number of times a tail recursive call
                % has reused this stack frame. Otherwise, it will be no.
                slot_tail_rec       :: maybe(int)
            ).

    % Return the set of input variables whose values should be preserved
    % until the exit and fail ports. This will be all the input variables,
    % except those that can be totally clobbered during the evaluation
    % of the procedure (those partially clobbered may still be of interest,
    % although to handle them properly we need to record insts in stack
    % layouts), and those of dummy types.
    %
:- pred trace_fail_vars(module_info::in, proc_info::in,
    set_of_progvar::out) is det.

    % Figure out whether we need a slot for storing the value of maxfr
    % on entry, and record the result in the proc info.
    %
:- pred do_we_need_maxfr_slot(eff_trace_level::in,
    proc_info::in, proc_info::out) is det.

    % Return the number of slots reserved for tracing information.
    % If there are N slots, the reserved slots will be 1 through N.
    %
    % It is possible that one of these reserved slots contains a variable.
    % If so, the variable and its slot number are returned in the last
    % argument.
    %
:- pred trace_reserved_slots(globals::in, proc_info::in, eff_trace_level::in,
    int::out, maybe(pair(prog_var, int))::out) is det.

    % Construct and return an abstract struct that represents the
    % tracing-specific part of the code generator state. Return also
    % info about the non-fixed slots used by the tracing system,
    % for eventual use in the constructing the procedure's layout structure.
    %
:- pred trace_setup(globals::in, proc_info::in, eff_trace_level::in,
    maybe(label)::in, trace_slot_info::out, trace_info::out,
    code_info::in, code_info::out) is det.

    % Generate code to fill in the reserved stack slots.
    %
:- pred generate_slot_fill_code(code_info::in, trace_info::in,
    llds_code::out) is det.

    % If we are doing execution tracing, generate code to prepare for a call.
    %
:- pred trace_prepare_for_call(code_info::in, llds_code::out) is det.

    % If we are doing execution tracing, generate code for an internal
    % trace event. This predicate must be called just before generating code
    % for the given goal.
    %
:- pred maybe_generate_internal_event_code(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 det.

    % If we are doing execution tracing, generate code for an trace event
    % that represents leaving a negated goal (via success or failure).
    %
:- pred maybe_generate_negated_event_code(hlds_goal::in,
    hlds_goal_info::in, negation_end_port::in, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

    % Generate code for a user-defined trace event.
    %
:- pred generate_user_event_code(user_event_info::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 external_event_info
    --->    external_event_info(
                % The label associated with the external event.
                label,

                % The map giving the locations of the typeinfo variables
                % needed to describe the types of the variables live at the
                % event.
                map(tvar, set(layout_locn)),

                % The code generated for the event.
                llds_code
            ).

    % Generate code for an external trace event.
    % Besides the trace code, we return the label on which we have hung
    % the trace liveness information and data on the type variables in the
    % liveness information, since some of our callers also need this
    % information.
    %
:- pred generate_external_event_code(external_trace_port::in,
    trace_info::in, prog_context::in, maybe(external_event_info)::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

:- pred generate_tailrec_event_code(trace_info::in,
    assoc_list(prog_var, arg_info)::in, goal_id::in, prog_context::in,
    llds_code::out, label::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

    % If the trace level calls for redo events, generate code that pushes
    % a temporary nondet stack frame whose redoip slot contains the
    % address of one of the labels in the runtime that calls MR_trace
    % for a redo event. Otherwise, generate empty code.
    %
:- pred maybe_setup_redo_event(trace_info::in, llds_code::out) is det.

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

:- implementation.

:- import_module backend_libs.
:- import_module backend_libs.builtin_ops.
:- import_module check_hlds.
:- import_module check_hlds.inst_test.
:- import_module check_hlds.mode_util.
:- import_module hlds.code_model.
:- import_module hlds.hlds_llds.
:- import_module hlds.hlds_proc_util.
:- import_module hlds.instmap.
:- import_module libs.options.
:- import_module ll_backend.code_util.
:- import_module ll_backend.layout_out.
:- import_module mdbcomp.prim_data.
:- import_module parse_tree.prog_data_foreign.
:- import_module parse_tree.prog_type.
:- import_module parse_tree.prog_type_scan.
:- import_module parse_tree.var_table.

:- import_module bool.
:- import_module cord.
:- import_module int.
:- import_module list.
:- import_module require.
:- import_module string.
:- import_module term.

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

    % Information for tracing that is valid throughout the execution
    % of a procedure.
:- type trace_info
    --->    trace_info(
                ti_eff_trace_level      :: eff_trace_level,
                ti_trace_suppress_items :: trace_suppress_items,

                % If the trace level is shallow, the lval of the slot
                % that holds the from-full flag.
                ti_from_full_lval       :: maybe(lval),

                % If the procedure has I/O state arguments, the lval
                % of the slot that holds the initial value of the
                % I/O action counter.
                ti_io_seq_lval          :: maybe(lval),

                % If trailing is enabled, the lvals of the slots that hold
                % the value of the trail pointer and the ticket counter
                % at the time of the call.
                ti_trail_lvals          :: maybe(pair(lval)),

                % If we reserve a slot for holding the value of maxfr
                % at entry for use in implementing retry, the lval of the slot.
                ti_maxfr_lval           :: maybe(lval),

                % If we reserve a slot for holding the value of the call table
                % tip variable, the lval of this variable.
                ti_call_table_tip_lval  :: maybe(lval),

                % If we reserve a slot for holding the number of times the
                % stack frame was reused by tail recursive calls, the lval
                % holding this counter, and the label that a tail recursive
                % call should jump to.
                ti_tail_rec_info        :: maybe(pair(lval, label)),

                % If we are generating redo events, this has the label
                % associated with the fail event, which we then reserve
                % in advance, so we can put the address of its layout struct
                % into the slot which holds the layout for the redo event
                % (the two events have identical layouts).
                ti_redo_label           :: maybe(label)
            ).

get_trace_maybe_tail_rec_info(TraceInfo, TraceInfo ^ ti_tail_rec_info).

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

    % Information specific to a trace port.
    %
:- type trace_port_info
    --->    port_info_external
    ;       port_info_tailrec_call(
                % The id of the tail recursive call.
                forward_goal_path,

                % The list of arguments of this call.
                assoc_list(prog_var, arg_info)
            )
    ;       port_info_internal(
                % The id of the goal whose start this port represents.
                forward_goal_path,

                % The pre-death set of this goal.
                set_of_progvar
            )
    ;       port_info_negation_end(
                % The id of the goal whose end (one way or another)
                % this port represents.
                forward_goal_path
            )
    ;       port_info_user(
                % The id of the goal.
                forward_goal_path
            ).

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

trace_fail_vars(ModuleInfo, ProcInfo, FailVars) :-
    proc_info_get_var_table(ProcInfo, VarTable),
    proc_info_get_headvars(ProcInfo, HeadVars),
    proc_info_get_argmodes(ProcInfo, Modes),
    mode_list_get_final_insts(ModuleInfo, Modes, FinalInsts),
    proc_info_arg_info(ProcInfo, ArgInfos),
    ( if
        build_fail_vars(ModuleInfo, VarTable, HeadVars, FinalInsts, ArgInfos,
            FailVarsList)
    then
        set_of_var.list_to_set(FailVarsList, FailVars)
    else
        unexpected($pred, "length mismatch")
    ).

:- pred build_fail_vars(module_info::in, var_table::in,
    list(prog_var)::in, list(mer_inst)::in, list(arg_info)::in,
    list(prog_var)::out) is semidet.

build_fail_vars(_, _, [], [], [], []).
build_fail_vars(ModuleInfo, VarTable,
        [Var | Vars], [Inst | Insts], [Info | Infos], FailVars) :-
    build_fail_vars(ModuleInfo, VarTable, Vars, Insts, Infos, FailVars0),
    Info = arg_info(_Loc, ArgMode),
    ( if
        ArgMode = top_in,
        not inst_is_clobbered(ModuleInfo, Inst),
        lookup_var_entry(VarTable, Var, Entry),
        Entry ^ vte_is_dummy = is_not_dummy_type
    then
        FailVars = [Var | FailVars0]
    else
        FailVars = FailVars0
    ).

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

do_we_need_maxfr_slot(EffTraceLevel, !ProcInfo) :-
    CodeModel = proc_info_interface_code_model(!.ProcInfo),
    ( if
        is_exec_trace_enabled_at_eff_trace_level(EffTraceLevel)
            = exec_trace_is_enabled,
        CodeModel \= model_non,
        proc_info_get_goal(!.ProcInfo, Goal),
        code_util.goal_may_alloc_temp_frame(Goal, yes)
    then
        NeedsMaxfrSlot = needs_maxfr_slot
    else
        NeedsMaxfrSlot = does_not_need_maxfr_slot
    ),
    proc_info_set_needs_maxfr_slot(NeedsMaxfrSlot, !ProcInfo).

    % trace_reserved_slots and trace_setup cooperate in the allocation of
    % stack slots for tracing purposes. The allocation is done in the
    % following stages.
    %
    % stage 1:  Allocate the fixed slots, slots 1, 2 and 3, to hold
    %           the event number of call, the call sequence number
    %           and the call depth respectively.
    %
    % stage 2:  If the procedure is model_non and --trace-redo is set,
    %           allocate the next available slot (which must be slot 4)
    %           to hold the address of the redo layout structure.
    %
    % stage 3:  If the procedure is shallow traced, allocate the
    %           next available slot to the saved copy of the
    %           from-full flag. The number of this slot is recorded
    %           in the maybe_from_full field in the proc layout;
    %           if there is no such slot, that field will contain -1.
    %
    % stage 4:  If --trace-table-io is given, allocate the next slot
    %           to hold the saved value of the io sequence number,
    %           for use in implementing retry. The number of this slot
    %           is recorded in the maybe_io_seq field in the proc
    %           layout; if there is no such slot, that field will
    %           contain -1.
    %
    % stage 5:  If --use-trail is set (given or implied), allocate
    %           two slots to hold the saved value of the trail pointer
    %           and the ticket counter at the point of the call, for
    %           use in implementing retry. The number of the first of
    %           these two slots is recorded in the maybe_trail field
    %           in the proc layout; if there are no such slots, that
    %           field will contain -1.
    %
    % stage 6:  If the procedure lives on the det stack but can put
    %           frames on the nondet stack, allocate a slot to hold
    %           the saved value of maxfr at the point of the call,
    %           for use in implementing retry. The number of this
    %           slot is recorded in the maybe_maxfr field in the proc
    %           layout; if there is no such slot, that field will
    %           contain -1.
    %
    % stage 7:  If the procedure has tail call events, we allocate a slot
    %           to hold a variable that counts how many tail events we have
    %           executed in this stack frame so far. The number of this slot
    %           is recorded in the maybe_tail_rec field in the proc layout;
    %           if there is no such slot, that field will contain -1.
    %
    % stage 8:  If the procedure's evaluation method is memo, loopcheck
    %           or minimal model, we allocate a slot to hold the
    %           variable that represents the tip of the call table.
    %           The debugger needs this, because when it executes a
    %           retry command, it must reset this tip to uninitialized.
    %           The number of this slot is recorded in the maybe_table
    %           field in the proc layout; if there is no such slot,
    %           that field will contain -1.
    %           (This should be the last stage; add new ones before this one.)
    %
    % If you add any more stages, please consider that any new slots
    % may need to be reset or adjusted before TAIL events.
    %
    % The procedure's layout structure does not need to include
    % information about the presence or absence of the slot holding
    % the address of the redo layout structure. If we generate redo
    % trace events, the runtime will know that this slot exists and
    % what its number must be; if we do not, the runtime will never
    % refer to such a slot.
    %
    % We need two redo labels in the runtime. Deep traced procedures
    % do not have a from-full slot, but their slots 1 through 4 are always
    % valid; the label handling their redos accesses those slots directly.
    % Shallow traced procedures do have a from-full slot, and their slots
    % 1-4 are valid only if the from-full slot is MR_TRUE; the label
    % handling their redos thus checks this slot to see whether it can
    % (or should) access the other slots. In shallow-traced model_non
    % procedures that generate redo events, the from-full flag is always
    % in slot 5.
    %
    % The slots allocated by stages 1 and 2 are only ever referred to
    % by the runtime system if they are guaranteed to exist. The runtime
    % system may of course also need to refer to slots allocated by later
    % stages, but before it does so, it needs to know whether those slots
    % exist or not. This is why setup returns TraceSlotInfo, which answers
    % such questions, for later inclusion in the procedure's layout structure.

trace_reserved_slots(Globals, ProcInfo, EffTraceLevel,
        ReservedSlots, MaybeTableVarInfo) :-
    globals.get_trace_suppress(Globals, TraceSuppress),
    globals.lookup_bool_option(Globals, trace_table_io, TraceTableIo),
    FixedSlots = eff_trace_level_needs_fixed_slots(EffTraceLevel),
    (
        FixedSlots = no,
        ReservedSlots = 0,
        MaybeTableVarInfo = no
    ;
        FixedSlots = yes,
        % Stage 1.
        Fixed = 3, % event#, call#, call depth
        % Stage 2.
        ( if
            proc_info_interface_code_model(ProcInfo) = model_non,
            eff_trace_level_needs_port(EffTraceLevel, TraceSuppress,
                port_redo) = yes
        then
            RedoLayout = 1
        else
            RedoLayout = 0
        ),
        % Stage 3.
        NeedFromFullSlot = eff_trace_level_needs_from_full_slot(EffTraceLevel),
        (
            NeedFromFullSlot = yes,
            FromFull = 1
        ;
            NeedFromFullSlot = no,
            FromFull = 0
        ),
        % Stage 4.
        (
            TraceTableIo = yes,
            IoSeq = 1
        ;
            TraceTableIo = no,
            IoSeq = 0
        ),
        % Stage 5.
        globals.lookup_bool_option(Globals, use_trail, UseTrail),
        (
            UseTrail = yes,
            Trail = 2
        ;
            UseTrail = no,
            Trail = 0
        ),
        % Stage 6.
        proc_info_get_needs_maxfr_slot(ProcInfo, NeedsMaxfrSlot),
        (
            NeedsMaxfrSlot = needs_maxfr_slot,
            Maxfr = 1
        ;
            NeedsMaxfrSlot = does_not_need_maxfr_slot,
            Maxfr = 0
        ),
        % Stage 7.
        proc_info_get_has_tail_rec_call(ProcInfo, HasTailRecCall),
        HasTailRecCall = has_tail_rec_call(HasSelfTailRecCall,
            _HasMutualTailRecCall),
        (
            HasSelfTailRecCall = has_self_tail_rec_call,
            TailRec = 1
        ;
            HasSelfTailRecCall = has_no_self_tail_rec_call,
            TailRec = 0
        ),
        ReservedSlots0 = Fixed + RedoLayout + FromFull + IoSeq + Trail +
            Maxfr + TailRec,
        % Stage 8.
        proc_info_get_call_table_tip(ProcInfo, MaybeCallTableVar),
        (
            MaybeCallTableVar = yes(CallTableVar),
            ReservedSlots = ReservedSlots0 + 1,
            MaybeTableVarInfo = yes(CallTableVar - ReservedSlots)
        ;
            MaybeCallTableVar = no,
            ReservedSlots = ReservedSlots0,
            MaybeTableVarInfo = no
        )
    ).

trace_setup(Globals, ProcInfo, EffTraceLevel, MaybeTailRecLabel,
        TraceSlotInfo, TraceInfo, !CI) :-
    CodeModel = get_proc_model(!.CI),
    globals.get_trace_suppress(Globals, TraceSuppress),
    globals.lookup_bool_option(Globals, trace_table_io, TraceTableIo),
    TraceRedo =
        eff_trace_level_needs_port(EffTraceLevel, TraceSuppress, port_redo),
    some [!NextSlot] (
        % Stages 1 and 2.
        ( if
            TraceRedo = yes,
            CodeModel = model_non
        then
            get_next_label(RedoLayoutLabel, !CI),
            MaybeRedoLayoutLabel = yes(RedoLayoutLabel),
            % We always reserve slots 1, 2 and 3, and we reserve slot 4
            % for the redo layout label.
            !:NextSlot = 5
        else
            MaybeRedoLayoutLabel = no,
            % We always reserve slots 1, 2 and 3.
            !:NextSlot = 4
        ),
        % Stage 3.
        HasFromFullSlot = eff_trace_level_needs_from_full_slot(EffTraceLevel),
        StackId = code_model_to_main_stack(CodeModel),
        (
            HasFromFullSlot = yes,
            FromFullSlot = !.NextSlot,
            MaybeFromFullSlot = yes(FromFullSlot),
            FromFullSlotLval = stack_slot_num_to_lval(StackId, FromFullSlot),
            MaybeFromFullSlotLval = yes(FromFullSlotLval),
            !:NextSlot = !.NextSlot + 1
        ;
            HasFromFullSlot = no,
            MaybeFromFullSlot = no,
            MaybeFromFullSlotLval = no
        ),
        % Stage 4.
        (
            TraceTableIo = yes,
            IoSeqSlot = !.NextSlot,
            MaybeIoSeqSlot = yes(IoSeqSlot),
            IoSeqLval = stack_slot_num_to_lval(StackId, IoSeqSlot),
            MaybeIoSeqLval = yes(IoSeqLval),
            !:NextSlot = !.NextSlot + 1
        ;
            TraceTableIo = no,
            MaybeIoSeqSlot = no,
            MaybeIoSeqLval = no
        ),
        % Stage 5.
        globals.lookup_bool_option(Globals, use_trail, UseTrail),
        (
            UseTrail = yes,
            TrailSlot = !.NextSlot,
            TicketSlot = !.NextSlot + 1,
            MaybeTrailSlot = yes(TrailSlot),
            TrailLval = stack_slot_num_to_lval(StackId, TrailSlot),
            TicketLval = stack_slot_num_to_lval(StackId, TicketSlot),
            MaybeTrailLvals = yes(TrailLval - TicketLval),
            !:NextSlot = !.NextSlot + 2
        ;
            UseTrail = no,
            MaybeTrailSlot = no,
            MaybeTrailLvals = no
        ),
        % Stage 6.
        proc_info_get_needs_maxfr_slot(ProcInfo, NeedsMaxfrSlot),
        (
            NeedsMaxfrSlot = needs_maxfr_slot,
            MaxfrSlot = !.NextSlot,
            MaybeMaxfrSlot = yes(MaxfrSlot),
            MaxfrLval = stack_slot_num_to_lval(StackId, MaxfrSlot),
            MaybeMaxfrLval = yes(MaxfrLval),
            !:NextSlot = !.NextSlot + 1
        ;
            NeedsMaxfrSlot = does_not_need_maxfr_slot,
            MaybeMaxfrSlot = no,
            MaybeMaxfrLval = no
        ),
        % Stage 7.
        (
            MaybeTailRecLabel = yes(TailRecLabel),
            TailRecSlot = !.NextSlot,
            MaybeTailRecSlot = yes(TailRecSlot),
            TailRecLval = stack_slot_num_to_lval(StackId, TailRecSlot),
            MaybeTailRecInfo = yes(TailRecLval - TailRecLabel),
            !:NextSlot = !.NextSlot + 1
        ;
            MaybeTailRecLabel = no,
            MaybeTailRecSlot = no,
            MaybeTailRecInfo = no
        ),
        % Stage 8.
        proc_info_get_call_table_tip(ProcInfo, MaybeCallTableTip),
        (
            MaybeCallTableTip = yes(_),
            CallTableSlot = !.NextSlot,
            MaybeCallTableSlot = yes(CallTableSlot),
            CallTableLval = stack_slot_num_to_lval(StackId, CallTableSlot),
            MaybeCallTableLval = yes(CallTableLval)
        ;
            MaybeCallTableTip = no,
            MaybeCallTableSlot = no,
            MaybeCallTableLval = no
        )
    ),
    TraceSlotInfo = trace_slot_info(MaybeFromFullSlot, MaybeIoSeqSlot,
        MaybeTrailSlot, MaybeMaxfrSlot, MaybeCallTableSlot, MaybeTailRecSlot),
    TraceInfo = trace_info(EffTraceLevel, TraceSuppress,
        MaybeFromFullSlotLval, MaybeIoSeqLval, MaybeTrailLvals,
        MaybeMaxfrLval, MaybeCallTableLval, MaybeTailRecInfo,
        MaybeRedoLayoutLabel).

generate_slot_fill_code(CI, TraceInfo, TraceCode) :-
    CodeModel = get_proc_model(CI),
    MaybeFromFullSlot  = TraceInfo ^ ti_from_full_lval,
    MaybeIoSeqSlot     = TraceInfo ^ ti_io_seq_lval,
    MaybeTrailLvals    = TraceInfo ^ ti_trail_lvals,
    MaybeMaxfrLval     = TraceInfo ^ ti_maxfr_lval,
    MaybeCallTableLval = TraceInfo ^ ti_call_table_tip_lval,
    MaybeTailRecInfo   = TraceInfo ^ ti_tail_rec_info,
    MaybeRedoLabel     = TraceInfo ^ ti_redo_label,
    event_num_slot(CodeModel, EventNumLval),
    call_num_slot(CodeModel, CallNumLval),
    call_depth_slot(CodeModel, CallDepthLval),
    stackref_to_string(EventNumLval, EventNumStr),
    stackref_to_string(CallNumLval, CallNumStr),
    stackref_to_string(CallDepthLval, CallDepthStr),
    some [!CodeStr] (
        % Stage 1.
        !:CodeStr = "\t\tMR_trace_fill_std_slots(" ++ EventNumStr ++ ", " ++
            CallNumStr ++ ", " ++ CallDepthStr ++ ");\n",
        % Stage 2.
        (
            MaybeRedoLabel = yes(RedoLayoutLabel),
            redo_layout_slot(CodeModel, RedoLayoutLval),
            stackref_to_string(RedoLayoutLval, RedoLayoutStr),
            !:CodeStr = !.CodeStr ++ "\t\t" ++ RedoLayoutStr ++
                " = (MR_Word) (const MR_Word *) MR_HASH_DEF_LABEL_LAYOUT;\n",
            MaybeLayoutLabel = yes(RedoLayoutLabel),
            MaybeHashDefLabel = yes(RedoLayoutLabel)
        ;
            MaybeRedoLabel = no,
            MaybeLayoutLabel = no,
            MaybeHashDefLabel = no
        ),
        % Stage 3 is handled later.
        % Stage 4.
        (
            MaybeIoSeqSlot = yes(IoSeqLval),
            stackref_to_string(IoSeqLval, IoSeqStr),
            !:CodeStr = !.CodeStr ++
                "\t\t" ++ IoSeqStr ++ " = MR_io_tabling_counter;\n"
        ;
            MaybeIoSeqSlot = no
        ),
        % Stage 5.
        (
            % This could be done by generating proper LLDS instead of C.
            % However, in shallow traced code we want to execute this
            % only when the caller is deep traced, and everything inside
            % that test must be in C code.
            MaybeTrailLvals = yes(TrailLval - TicketLval),
            stackref_to_string(TrailLval, TrailLvalStr),
            stackref_to_string(TicketLval, TicketLvalStr),
            !:CodeStr = !.CodeStr ++
                "\t\tMR_mark_ticket_stack(" ++ TicketLvalStr ++ ");\n" ++
                "\t\tMR_store_ticket(" ++ TrailLvalStr ++ ");\n"
        ;
            MaybeTrailLvals = no
        ),
        % Stage 3 (delayed).
        (
            MaybeFromFullSlot = yes(CallFromFullSlot),
            stackref_to_string(CallFromFullSlot, CallFromFullSlotStr),
            !:CodeStr =
                "\t\t" ++ CallFromFullSlotStr ++ " = MR_trace_from_full;\n" ++
                "\t\tif (MR_trace_from_full) {\n" ++
                !.CodeStr ++
                "\t\t} else {\n" ++
                "\t\t\t" ++ CallDepthStr ++ " = MR_trace_call_depth;\n" ++
                "\t\t}\n"
        ;
            MaybeFromFullSlot = no
        ),
        TraceStmt1 = !.CodeStr
    ),
    TraceComponents1 = [foreign_proc_raw_code(cannot_branch_away,
        proc_does_not_affect_liveness, live_lvals_info(set.init), TraceStmt1)],
    TraceCode1 = singleton(
        llds_instr(foreign_proc_code([], TraceComponents1,
            proc_will_not_call_mercury, no, no, MaybeLayoutLabel,
            no, MaybeHashDefLabel, refers_to_llds_stack,
            proc_may_not_duplicate), "")
    ),
    % Stage 6.
    (
        MaybeMaxfrLval = yes(MaxfrLval),
        TraceCode2 = singleton(
            llds_instr(assign(MaxfrLval, lval(maxfr)), "save initial maxfr")
        )
    ;
        MaybeMaxfrLval = no,
        TraceCode2 = empty
    ),
    % Stage 7.
    (
        MaybeTailRecInfo = yes(TailRecLval - _TailRecLabel),
        stackref_to_string(TailRecLval, TailRecLvalStr),
        TraceStmt3 =
            "\t\tif (MR_trace_tailrec_have_reused_frames) {\n" ++
            "\t\t\t" ++ TailRecLvalStr ++
                " = MR_trace_tailrec_num_reused_frames;\n" ++
            "\t\t\tMR_trace_tailrec_have_reused_frames = MR_FALSE;\n" ++
            "\t\t} else {" ++
            "\t\t\t" ++ TailRecLvalStr ++ " = 0;\n" ++
            "\t\t}",
        TraceComponents3 = [foreign_proc_raw_code(cannot_branch_away,
            proc_does_not_affect_liveness, live_lvals_info(set.init),
            TraceStmt3)],
        TraceCode3 = singleton(
            llds_instr(foreign_proc_code([], TraceComponents3,
                proc_will_not_call_mercury, no, no, no, no, no,
                refers_to_llds_stack, proc_may_not_duplicate),
                "initialize tail recursion count")
        )
    ;
        MaybeTailRecInfo = no,
        TraceCode3 = empty
    ),
    % Stage 8.
    (
        MaybeCallTableLval = yes(CallTableLval),
        stackref_to_string(CallTableLval, CallTableLvalStr),
        TraceStmt4 = "\t\t" ++ CallTableLvalStr ++ " = 0;\n",
        TraceComponents4 = [foreign_proc_raw_code(cannot_branch_away,
            proc_does_not_affect_liveness, live_lvals_info(set.init),
            TraceStmt4)],
        TraceCode4 = singleton(
            llds_instr(foreign_proc_code([], TraceComponents4,
                proc_will_not_call_mercury, no, no, no, no, no,
                refers_to_llds_stack, proc_may_not_duplicate), "")
        )
    ;
        MaybeCallTableLval = no,
        TraceCode4 = empty
    ),
    TraceCode = TraceCode1 ++ TraceCode2 ++ TraceCode3 ++ TraceCode4.

trace_prepare_for_call(CI, TraceCode) :-
    get_maybe_trace_info(CI, MaybeTraceInfo),
    CodeModel = get_proc_model(CI),
    (
        MaybeTraceInfo = yes(TraceInfo),
        MaybeFromFullSlot = TraceInfo ^ ti_from_full_lval,
        call_depth_slot(CodeModel, CallDepthLval),
        stackref_to_string(CallDepthLval, CallDepthStr),
        (
            MaybeFromFullSlot = yes(_),
            MacroStr = "MR_trace_reset_depth_from_shallow"
        ;
            MaybeFromFullSlot = no,
            MacroStr = "MR_trace_reset_depth_from_full"
        ),
        ResetStmt = MacroStr ++ "(" ++ CallDepthStr ++ ");\n",
        TraceCode = singleton(
            llds_instr(arbitrary_c_code(proc_does_not_affect_liveness,
                live_lvals_info(set.init), ResetStmt), "")
        )
    ;
        MaybeTraceInfo = no,
        TraceCode = empty
    ).

maybe_generate_internal_event_code(Goal, OutsideGoalInfo, Code, !CI, !CLD) :-
    get_maybe_trace_info(!.CI, MaybeTraceInfo),
    (
        MaybeTraceInfo = yes(TraceInfo),
        Goal = hlds_goal(_, GoalInfo),
        GoalId = goal_info_get_goal_id(GoalInfo),
        get_containing_goal_map(!.CI, ContainingGoalMap),
        map.lookup(ContainingGoalMap, GoalId, ContainingGoal),
        ( if
            ContainingGoal = containing_goal(_, LastStep),
            (
                LastStep = step_switch(_, _),
                PortPrime = port_switch
            ;
                LastStep = step_disj(DisjunctNum),
                ( if DisjunctNum = 1 then
                    PortPrime = port_disj_first
                else
                    PortPrime = port_disj_later
                )
            ;
                LastStep = step_ite_cond,
                PortPrime = port_ite_cond
            ;
                LastStep = step_ite_then,
                PortPrime = port_ite_then
            ;
                LastStep = step_ite_else,
                PortPrime = port_ite_else
            ;
                LastStep = step_neg,
                PortPrime = port_neg_enter
            )
        then
            Port = PortPrime
        else
            unexpected($pred, "bad path")
        ),
        EffTraceLevel = TraceInfo ^ ti_eff_trace_level,
        TraceSuppress = TraceInfo ^ ti_trace_suppress_items,
        NeedsPort =
            eff_trace_level_needs_port(EffTraceLevel, TraceSuppress, Port),
        (
            NeedsPort = yes,
            goal_info_get_pre_deaths(GoalInfo, PreDeaths),
            Context = goal_info_get_context(GoalInfo),
            ( if
                goal_info_has_feature(OutsideGoalInfo,
                    feature_hide_debug_event)
            then
                HideEvent = yes
            else
                HideEvent = no
            ),
            GoalPath = goal_id_to_forward_path(ContainingGoalMap, GoalId),
            generate_event_code(Port, port_info_internal(GoalPath, PreDeaths),
                yes(TraceInfo), Context, HideEvent, no, _, _, Code, !CI, !CLD)
        ;
            NeedsPort = no,
            Code = empty
        )
    ;
        MaybeTraceInfo = no,
        Code = empty
    ).

maybe_generate_negated_event_code(Goal, OutsideGoalInfo, NegPort, Code,
        !CI, !CLD) :-
    get_maybe_trace_info(!.CI, MaybeTraceInfo),
    ( if
        MaybeTraceInfo = yes(TraceInfo),
        (
            NegPort = neg_failure,
            Port = port_neg_failure
        ;
            NegPort = neg_success,
            Port = port_neg_success
        ),
        EffTraceLevel = TraceInfo ^ ti_eff_trace_level,
        TraceSuppress = TraceInfo ^ ti_trace_suppress_items,
        eff_trace_level_needs_port(EffTraceLevel, TraceSuppress, Port) = yes
    then
        Goal = hlds_goal(_, GoalInfo),
        GoalId = goal_info_get_goal_id(GoalInfo),
        Context = goal_info_get_context(GoalInfo),
        ( if
            goal_info_has_feature(OutsideGoalInfo, feature_hide_debug_event)
        then
            HideEvent = yes
        else
            HideEvent = no
        ),
        get_containing_goal_map(!.CI, ContainingGoalMap),
        GoalPath = goal_id_to_forward_path(ContainingGoalMap, GoalId),
        generate_event_code(Port, port_info_negation_end(GoalPath),
            yes(TraceInfo), Context, HideEvent, no, _, _, Code, !CI, !CLD)
    else
        Code = empty
    ).

generate_user_event_code(UserInfo, GoalInfo, Code, !CI, !CLD) :-
    GoalId = goal_info_get_goal_id(GoalInfo),
    get_containing_goal_map(!.CI, ContainingGoalMap),
    GoalPath = goal_id_to_forward_path(ContainingGoalMap, GoalId),
    Context = goal_info_get_context(GoalInfo),
    Port = port_user,
    PortInfo = port_info_user(GoalPath),
    MaybeTraceInfo = no,
    HideEvent = no,
    generate_event_code(Port, PortInfo, MaybeTraceInfo, Context, HideEvent,
        yes(UserInfo), _Label, _TVarDataMap, Code, !CI, !CLD).

generate_external_event_code(ExternalPort, TraceInfo, Context,
        MaybeExternalInfo, !CI, !CLD) :-
    Port = convert_external_port_type(ExternalPort),
    EffTraceLevel = TraceInfo ^ ti_eff_trace_level,
    TraceSuppress = TraceInfo ^ ti_trace_suppress_items,
    NeedPort = eff_trace_level_needs_port(EffTraceLevel, TraceSuppress, Port),
    (
        NeedPort = yes,
        generate_event_code(Port, port_info_external, yes(TraceInfo), Context,
            no, no, Label, TVarDataMap, Code, !CI, !CLD),
        MaybeExternalInfo = yes(external_event_info(Label, TVarDataMap, Code))
    ;
        NeedPort = no,
        MaybeExternalInfo = no
    ).

generate_tailrec_event_code(TraceInfo, ArgsInfos, GoalId, Context, Code,
        TailRecLabel, !CI, !CLD) :-
    Port = port_tailrec_call,
    get_containing_goal_map(!.CI, ContainingGoalMap),
    GoalPath = goal_id_to_forward_path(ContainingGoalMap, GoalId),
    PortInfo = port_info_tailrec_call(GoalPath, ArgsInfos),
    HideEvent = no,
    MaybeUserInfo = no,
    generate_event_code(Port, PortInfo, yes(TraceInfo), Context, HideEvent,
        MaybeUserInfo, _Label, _TVarDataMap, Code, !CI, !CLD),
    MaybeTailRecInfo = TraceInfo ^ ti_tail_rec_info,
    (
        MaybeTailRecInfo = yes(_ - TailRecLabel)
    ;
        MaybeTailRecInfo = no,
        unexpected($pred, "no tail rec label")
    ).

:- pred generate_tailrec_reset_slots_code(trace_info::in,
    llds_code::out, code_info::in, code_info::out) is det.

generate_tailrec_reset_slots_code(TraceInfo, Code, !CI) :-
    % We reset all the debugging slots that need to be reset. We handle them
    % in the order of allocation.
    % Stage 1.
    CodeModel = get_proc_model(!.CI),
    event_num_slot(CodeModel, EventNumLval),
    call_num_slot(CodeModel, CallNumLval),
    call_depth_slot(CodeModel, CallDepthLval),
    stackref_to_string(EventNumLval, EventNumStr),
    stackref_to_string(CallNumLval, CallNumStr),
    stackref_to_string(CallDepthLval, CallDepthStr),
    StdSlotCodeStr = "\t\tMR_trace_tailrec_std_slots(" ++
        EventNumStr ++ ", " ++ CallNumStr ++ ", " ++ CallDepthStr ++ ");\n",
    % Stage 2.
    % Tail recursion events cannot happen in model_non procedures, so stage 2
    % will not allocate any slots.
    MaybeRedoLabelLval = TraceInfo ^ ti_redo_label,
    expect(unify(MaybeRedoLabelLval, no), $pred,
        "redo label in procedure with TAIL event"),
    % Stage 3.
    % Tail recursion events are disabled if the trace level is shallow tracing,
    % so stage 3 will not allocate any slots.
    MaybeFromFullLval = TraceInfo ^ ti_from_full_lval,
    expect(unify(MaybeFromFullLval, no), $pred,
        "from_full slot in procedure with TAIL event"),
    % Stage 4.
    MaybeIoSeqSlot = TraceInfo ^ ti_io_seq_lval,
    (
        MaybeIoSeqSlot = yes(IoSeqLval),
        stackref_to_string(IoSeqLval, IoSeqStr),
        IoSeqCodeStr = "\t\t" ++ IoSeqStr ++ " = MR_io_tabling_counter;\n"
    ;
        MaybeIoSeqSlot = no,
        IoSeqCodeStr = ""
    ),
    % Stage 5.
    MaybeTrailLvals = TraceInfo ^ ti_trail_lvals,
    (
        MaybeTrailLvals = yes(TrailLval - TicketLval),
        stackref_to_string(TrailLval, TrailLvalStr),
        stackref_to_string(TicketLval, TicketLvalStr),
        TrailCodeStr =
            "\t\tMR_mark_ticket_stack(" ++ TicketLvalStr ++ ");\n" ++
            "\t\tMR_store_ticket(" ++ TrailLvalStr ++ ");\n"
    ;
        MaybeTrailLvals = no,
        TrailCodeStr = ""
    ),
    % Stage 6.
    MaybeMaxfrLval = TraceInfo ^ ti_maxfr_lval,
    (
        MaybeMaxfrLval = yes(MaxfrLval),
        MaxfrCode = singleton(
            llds_instr(assign(MaxfrLval, lval(maxfr)), "save initial maxfr")
        )
    ;
        MaybeMaxfrLval = no,
        MaxfrCode = empty
    ),
    % Stage 7.
    TailRecInfo = TraceInfo ^ ti_tail_rec_info,
    (
        TailRecInfo = yes(TailRecLval - _),
        TailRecLvalCode = singleton(
            llds_instr(assign(TailRecLval,
                binop(int_arith(int_type_int, ao_add),
                    lval(TailRecLval), const(llconst_int(1)))),
                "increment tail recursion counter")
        )
    ;
        TailRecInfo = no,
        unexpected($pred, "no tail rec lval")
    ),
    % Stage 8.
    MaybeCallTableLval = TraceInfo ^ ti_call_table_tip_lval,
    (
        MaybeCallTableLval = yes(CallTableLval),
        stackref_to_string(CallTableLval, CallTableLvalStr),
        CallTableCodeStr = "\t\t" ++ CallTableLvalStr ++ " = 0;\n"
    ;
        MaybeCallTableLval = no,
        CallTableCodeStr = ""
    ),
    ForeignLangCodeStr = StdSlotCodeStr ++ IoSeqCodeStr ++ TrailCodeStr ++
        CallTableCodeStr,
    ForeignLangComponents = [foreign_proc_raw_code(cannot_branch_away,
        proc_does_not_affect_liveness, live_lvals_info(set.init),
        ForeignLangCodeStr)],
    ForeignLangCode = singleton(
        llds_instr(foreign_proc_code([], ForeignLangComponents,
            proc_will_not_call_mercury, no, no, no, no, no,
            refers_to_llds_stack, proc_may_duplicate), "")
    ),
    Code = ForeignLangCode ++ MaxfrCode ++ TailRecLvalCode.

:- pred generate_event_code(trace_port::in, trace_port_info::in,
    maybe(trace_info)::in, prog_context::in, bool::in,
    maybe(user_event_info)::in, label::out,
    map(tvar, set(layout_locn))::out, llds_code::out,
    code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.

generate_event_code(Port, PortInfo, MaybeTraceInfo, Context, HideEvent,
        MaybeUserInfo, Label, TVarDataMap, Code, !CI, !CLD) :-
    get_next_label(Label, !CI),
    get_known_variables(!.CLD, LiveVars0),
    (
        PortInfo = port_info_external,
        LiveVars = LiveVars0,
        Path = fgp_nil,
        TailRecResetCode = empty
    ;
        PortInfo = port_info_tailrec_call(Path, ArgsInfos),
        % The pre_goal_update has added the output variables of the recursive
        % call to the set of live variables; we must undo this.
        find_output_vars(ArgsInfos, [], OutputVars),
        list.delete_elems(LiveVars0, OutputVars, LiveVars),
        (
            MaybeTraceInfo = yes(TailRecTraceInfo),
            generate_tailrec_reset_slots_code(TailRecTraceInfo,
                TailRecResetCode, !CI)
        ;
            MaybeTraceInfo = no,
            unexpected($pred, "tailrec call without TraceInfo")
        )
    ;
        PortInfo = port_info_internal(Path, PreDeaths),
        ResumeVars = current_resume_point_vars(!.CLD),
        set_of_var.difference(PreDeaths, ResumeVars, RealPreDeaths),
        RealPreDeathList = set_of_var.to_sorted_list(RealPreDeaths),
        list.delete_elems(LiveVars0, RealPreDeathList, LiveVars),
        TailRecResetCode = empty
    ;
        PortInfo = port_info_negation_end(Path),
        LiveVars = LiveVars0,
        TailRecResetCode = empty
    ;
        PortInfo = port_info_user(Path),
        LiveVars = LiveVars0,
        TailRecResetCode = empty
    ),
    get_var_table(!.CI, VarTable),
    get_instmap(!.CLD, InstMap),
    trace_produce_vars(LiveVars, VarTable, InstMap, Port,
        set.init, TVarSet, [], VarInfoList, ProduceCode, !CLD),
    max_reg_in_use(!.CLD, MaxRegR, MaxRegF),
    get_max_regs_in_use_at_trace(!.CI, MaxTraceRegR0, MaxTraceRegF0),
    int.max(MaxRegR, MaxTraceRegR0, MaxTraceRegR),
    int.max(MaxRegF, MaxTraceRegF0, MaxTraceRegF),
    ( if
        MaxTraceRegR0 = MaxTraceRegR,
        MaxTraceRegF0 = MaxTraceRegF
    then
        true
    else
        set_max_regs_in_use_at_trace(MaxTraceRegR, MaxTraceRegF, !CI)
    ),
    variable_locations(!.CLD, VarLocs),
    get_proc_info(!.CI, ProcInfo),
    proc_info_get_rtti_varmaps(ProcInfo, RttiVarMaps),
    set.to_sorted_list(TVarSet, TVarList),
    continuation_info.find_typeinfos_for_tvars(VarTable, RttiVarMaps,
        TVarList, VarLocs, TVarDataMap),

    % Compute the set of live lvals at the event.
    VarLvals = list.map(find_lval_in_var_info, VarInfoList),
    map.values(TVarDataMap, TVarLocnSets),
    TVarLocnSet = set.union_list(TVarLocnSets),
    set.to_sorted_list(TVarLocnSet, TVarLocns),
    TVarLvals = list.map(find_lval_in_layout_locn, TVarLocns),
    list.append(VarLvals, TVarLvals, LiveLvals),
    LiveLvalSet = set.list_to_set(LiveLvals),

    set.list_to_set(VarInfoList, VarInfoSet),
    LayoutLabelInfo = layout_label_info(VarInfoSet, TVarDataMap),
    (
        MaybeUserInfo = no,
        TraceStmt0 = "\t\tMR_EVENT_SYS\n"
    ;
        MaybeUserInfo = yes(_),
        TraceStmt0 = "\t\tMR_EVENT_USER\n"
    ),
    TraceStmt = "\t\t/* port " ++ trace_port_to_string(Port) ++ ", " ++
        "path <" ++ goal_path_to_string(Path) ++ "> */\n" ++
        TraceStmt0,

    add_trace_layout_for_label(Label, Context, Port, HideEvent,
        Path, MaybeUserInfo, LayoutLabelInfo, !CI),
    set_proc_trace_events(yes, !CI),
    ( if
        Port = port_fail,
        MaybeTraceInfo = yes(TraceInfo),
        TraceInfo ^ ti_redo_label = yes(RedoLabel)
    then
        % The layout information for the redo event is the same as for the
        % fail event; all the non-clobbered inputs in their stack slots.
        % It is convenient to generate this common layout when the code
        % generator state is set up for the fail event; generating it for
        % the redo event would be much harder. On the other hand, the address
        % of the layout structure for the redo event should be put into its
        % fixed stack slot at procedure entry. Therefore setup reserves a label
        % for the redo event, whose layout information is filled in when
        % we get to the fail event.
        add_trace_layout_for_label(RedoLabel, Context, port_redo,
            HideEvent, Path, no, LayoutLabelInfo, !CI)
    else
        true
    ),
    TraceComponents = [foreign_proc_raw_code(cannot_branch_away,
        proc_does_not_affect_liveness, live_lvals_info(LiveLvalSet),
        TraceStmt)],
    TraceCode =
        from_list([
            llds_instr(label(Label),
                "A label to hang trace liveness on"),
                % Referring to the label from the foreign_proc_code
                % prevents the label from being renamed or optimized away.
                % The label is before the trace code because sometimes this
                % pair is preceded by another label, and this way we can
                % eliminate this other label.
            llds_instr(foreign_proc_code([], TraceComponents,
                proc_may_call_mercury, no, no, yes(Label), no, yes(Label),
                refers_to_llds_stack, proc_may_not_duplicate), "")
        ]),
    Code = ProduceCode ++ TailRecResetCode ++ TraceCode.

:- pred find_output_vars(assoc_list(prog_var, arg_info)::in,
    list(prog_var)::in, list(prog_var)::out) is det.

find_output_vars([], !OutputVars).
find_output_vars([Arg - Info | ArgsInfos], !OutputVars) :-
    Info = arg_info(_ArgLoc, Mode),
    (
        Mode = top_out,
        !:OutputVars = [Arg | !.OutputVars]
    ;
        Mode = top_in
    ;
        Mode = top_unused
    ),
    find_output_vars(ArgsInfos, !OutputVars).

:- func find_lval_in_var_info(layout_var_info) = lval.

find_lval_in_var_info(layout_var_info(LayoutLocn, _, _)) =
    find_lval_in_layout_locn(LayoutLocn).

:- func find_lval_in_layout_locn(layout_locn) = lval.

find_lval_in_layout_locn(locn_direct(Lval)) = Lval.
find_lval_in_layout_locn(locn_indirect(Lval, _)) = Lval.

maybe_setup_redo_event(TraceInfo, Code) :-
    TraceRedoLabel = TraceInfo ^ ti_redo_label,
    (
        TraceRedoLabel = yes(_),
        MaybeFromFullSlot = TraceInfo ^ ti_from_full_lval,
        (
            MaybeFromFullSlot = yes(Lval),
            % The code in the runtime looks for the from-full flag in
            % framevar 5; see the comment before reserved_slots.
            expect(unify(Lval, framevar(5)), $pred,
                "from-full flag not stored in expected slot"),
            Code = singleton(
                llds_instr(mkframe(temp_frame(nondet_stack_proc),
                    yes(do_trace_redo_fail_shallow)),
                    "set up shallow redo event")
            )
        ;
            MaybeFromFullSlot = no,
            Code = singleton(
                llds_instr(mkframe(temp_frame(nondet_stack_proc),
                    yes(do_trace_redo_fail_deep)),
                    "set up deep redo event")
            )
        )
    ;
        TraceRedoLabel = no,
        Code = empty
    ).

:- pred trace_produce_vars(list(prog_var)::in, var_table::in,
    instmap::in, trace_port::in, set(tvar)::in, set(tvar)::out,
    list(layout_var_info)::in, list(layout_var_info)::out,
    llds_code::out, code_loc_dep::in, code_loc_dep::out) is det.

trace_produce_vars([], _, _, _, !TVars, !VarInfos, empty, !CLD).
trace_produce_vars([Var | Vars], VarTable, InstMap, Port,
        !TVars, !VarInfos, VarCode ++ VarsCode, !CLD) :-
    lookup_var_entry(VarTable, Var, Entry),
    IsDummy = Entry ^ vte_is_dummy,
    (
        IsDummy = is_dummy_type,
        VarCode = empty
    ;
        IsDummy = is_not_dummy_type,
        trace_produce_var(Var, Entry, InstMap, !TVars, VarInfo, VarCode, !CLD),
        !:VarInfos = [VarInfo | !.VarInfos]
    ),
    trace_produce_vars(Vars, VarTable, InstMap, Port, !TVars,
        !VarInfos, VarsCode, !CLD).

:- pred trace_produce_var(prog_var::in, var_table_entry::in, instmap::in,
    set(tvar)::in, set(tvar)::out, layout_var_info::out, llds_code::out,
    code_loc_dep::in, code_loc_dep::out) is det.

trace_produce_var(Var, Entry, _InstMap, !TVars, VarInfo, VarCode, !CLD) :-
    produce_variable_in_reg_or_stack(Var, VarCode, Lval, !CLD),
    Entry = vte(Name, Type, _),
%   get_module_info(!.CI, ModuleInfo),
%   instmap_lookup_var(InstMap, Var, Inst),
%   ( if inst_match.inst_is_ground(ModuleInfo, Inst) then
%       LldsInst = llds_inst_ground
%   else
%       LldsInst = llds_inst_partial(Inst)
%   ),
    LldsInst = llds_inst_better_be_ground,
    LiveType = live_value_var(Var, Name, Type, LldsInst),
    VarInfo = layout_var_info(locn_direct(Lval), LiveType, "trace"),
    type_vars_in_type(Type, TypeVars),
    set.insert_list(TypeVars, !TVars).

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

:- pred stackref_to_string(lval::in, string::out) is det.

stackref_to_string(Lval, LvalStr) :-
    ( if Lval = stackvar(Slot) then
        string.int_to_string(Slot, SlotString),
        LvalStr = "MR_sv(" ++ SlotString ++ ")"
    else if Lval = framevar(Slot) then
        string.int_to_string(Slot, SlotString),
        LvalStr = "MR_fv(" ++ SlotString ++ ")"
    else if Lval = double_stackvar(_, _) then
        % XXX how do we get here?
        sorry($pred, "double-width stack slot")
    else
        unexpected($pred, "non-stack lval")
    ).

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

:- func convert_external_port_type(external_trace_port) = trace_port.

convert_external_port_type(external_port_call) = port_call.
convert_external_port_type(external_port_exit) = port_exit.
convert_external_port_type(external_port_fail) = port_fail.
convert_external_port_type(external_port_tailrec_call) = port_tailrec_call.

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

:- pred event_num_slot(code_model::in, lval::out) is det.
:- pred call_num_slot(code_model::in, lval::out) is det.
:- pred call_depth_slot(code_model::in, lval::out) is det.
:- pred redo_layout_slot(code_model::in, lval::out) is det.

event_num_slot(CodeModel, EventNumSlot) :-
    (
        CodeModel = model_non,
        EventNumSlot  = framevar(1)
    ;
        ( CodeModel = model_det
        ; CodeModel = model_semi
        ),
        EventNumSlot  = stackvar(1)
    ).

call_num_slot(CodeModel, CallNumSlot) :-
    (
        CodeModel = model_non,
        CallNumSlot   = framevar(2)
    ;
        ( CodeModel = model_det
        ; CodeModel = model_semi
        ),
        CallNumSlot   = stackvar(2)
    ).

call_depth_slot(CodeModel, CallDepthSlot) :-
    (
        CodeModel = model_non,
        CallDepthSlot = framevar(3)
    ;
        ( CodeModel = model_det
        ; CodeModel = model_semi
        ),
        CallDepthSlot = stackvar(3)
    ).

redo_layout_slot(CodeModel, RedoLayoutSlot) :-
    (
        CodeModel = model_non,
        RedoLayoutSlot = framevar(4)
    ;
        ( CodeModel = model_det
        ; CodeModel = model_semi
        ),
        unexpected($pred,
            "attempt to access redo layout slot for det or semi procedure")
    ).

%-----------------------------------------------------------------------------%
:- end_module ll_backend.trace_gen.
%-----------------------------------------------------------------------------%
