%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1997-2012 The University of Melbourne.
% Copyright (C) 2015-2021 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: table_gen.m.
% Main authors: zs, ohutch.
%
% This module transforms HLDS code to implement loop detection, memoing,
% minimal model evaluation, or I/O idempotence. The transformation involves
% adding calls to predicates defined in library/table_builtin.m and in
% runtime/mercury_minimal_model.c.
%
% The loop detection transformation adds code to a procedure that allows
% early detection of infinite loops. If such loops are detected the program
% will terminate with a helpful error message.
%
% The memo transformation adds code that allows a procedure to "memo"
% (remember) answers once they have been generated using program clause
% resolution.
%
% The minimal model transformation changes the semantics of the procedure
% being transformed. See the PhD thesis of K. Sagonas: `The SLG-WAM: A
% Search-Efficient Engine for Well-Founded Evaluation of Normal Logic
% Programs' from SUNY at Stony Brook in 1996 for a description of
% the semantics behind the transformation. Currently only SLGd is
% implemented.
%
% The current implementation attempts to detect cases where tabling has
% undesirable interactions with if-then-else, solutions, and (possibly)
% negated contexts in general. However, the detection is done at runtime,
% since there is no known way of doing this compile time.
%
%---------------------------------------------------------------------------%

:- module transform_hlds.table_gen.
:- interface.

:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.

:- import_module list.

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

:- pred table_gen_process_module(module_info::in, module_info::out,
    list(error_spec)::in, list(error_spec)::out) is det.

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

:- implementation.

:- import_module check_hlds.
:- import_module check_hlds.mode_test.
:- import_module check_hlds.mode_util.
:- import_module check_hlds.polymorphism_type_info.
:- import_module check_hlds.purity.
:- import_module check_hlds.recompute_instmap_deltas.
:- import_module check_hlds.type_util.
:- import_module hlds.code_model.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_error_util.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_pred.
:- import_module hlds.hlds_proc_util.
:- import_module hlds.hlds_rtti.
:- import_module hlds.instmap.
:- import_module hlds.make_goal.
:- import_module hlds.pred_name.
:- import_module hlds.pred_table.
:- import_module hlds.status.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module ll_backend.
:- import_module ll_backend.continuation_info.
:- import_module mdbcomp.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.prim_data.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.builtin_lib_types.
:- import_module parse_tree.parse_tree_out_misc.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_data_foreign.
:- import_module parse_tree.prog_data_pragma.
:- import_module parse_tree.prog_mode.
:- import_module parse_tree.prog_type.
:- import_module parse_tree.prog_type_construct.
:- import_module parse_tree.prog_type_scan.
:- import_module parse_tree.prog_type_test.
:- import_module parse_tree.prog_util.
:- import_module parse_tree.set_of_var.
:- import_module parse_tree.var_table.

:- import_module assoc_list.
:- import_module bool.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module one_or_more.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module solutions.
:- import_module string.
:- import_module table_builtin.
:- import_module term.
:- import_module unit.
:- import_module varset.

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

    % Values of this type map the pred_id of a minimal_model tabled
    % predicate to the pred_id of its generator variant.
    %
:- type generator_map   ==  map(pred_id, pred_id).

    % NOTE: following preds seem to duplicate the code in passes_aux.m.
    % The reason for this duplication is that this module needs a variant
    % of this code that is able to handle passing a module_info to
    % polymorphism and getting an updated module_info back.
    %
table_gen_process_module(!ModuleInfo, !Specs) :-
    module_info_get_globals(!.ModuleInfo, Globals),
    globals.lookup_bool_option(Globals, trace_table_io, TraceTableIO),
    module_info_get_pred_id_table(!.ModuleInfo, PredIdTable0),
    map.keys(PredIdTable0, PredIds),
    map.init(GenMap0),
    table_gen_process_preds(TraceTableIO, PredIds,
        !ModuleInfo, GenMap0, _, !Specs).

:- pred table_gen_process_preds(bool::in, list(pred_id)::in,
    module_info::in, module_info::out,
    generator_map::in, generator_map::out,
    list(error_spec)::in, list(error_spec)::out) is det.

table_gen_process_preds(_, [], !ModuleInfo, !GenMap, !Specs).
table_gen_process_preds(TraceTableIO, [PredId | PredIds],
        !ModuleInfo, !GenMap, !Specs) :-
    table_gen_process_pred(TraceTableIO, PredId,
        !ModuleInfo, !GenMap, !Specs),
    table_gen_process_preds(TraceTableIO, PredIds,
        !ModuleInfo, !GenMap, !Specs).

:- pred table_gen_process_pred(bool::in, pred_id::in,
    module_info::in, module_info::out, generator_map::in, generator_map::out,
    list(error_spec)::in, list(error_spec)::out) is det.

table_gen_process_pred(TraceTableIO, PredId, !ModuleInfo, !GenMap, !Specs) :-
    module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
    ProcIds = pred_info_all_procids(PredInfo),
    table_gen_process_procs(TraceTableIO, PredId, ProcIds,
        !ModuleInfo, !GenMap, !Specs).

:- pred table_gen_process_procs(bool::in, pred_id::in, list(proc_id)::in,
    module_info::in, module_info::out,
    generator_map::in, generator_map::out,
    list(error_spec)::in, list(error_spec)::out) is det.

table_gen_process_procs(_, _, [], !ModuleInfo, !GenMap, !Specs).
table_gen_process_procs(TraceTableIO, PredId, [ProcId | ProcIds],
        !ModuleInfo, !GenMap, !Specs) :-
    module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
    pred_info_get_proc_table(PredInfo, ProcTable),
    map.lookup(ProcTable, ProcId, ProcInfo0),
    table_gen_process_proc(TraceTableIO, PredId, ProcId, ProcInfo0, PredInfo,
        !ModuleInfo, !GenMap, !Specs),
    table_gen_process_procs(TraceTableIO, PredId, ProcIds,
        !ModuleInfo, !GenMap, !Specs).

:- pred table_gen_process_proc(bool::in,
    pred_id::in, proc_id::in, proc_info::in, pred_info::in,
    module_info::in, module_info::out, generator_map::in, generator_map::out,
    list(error_spec)::in, list(error_spec)::out) is det.

table_gen_process_proc(TraceTableIO, PredId, ProcId, ProcInfo0, PredInfo0,
        !ModuleInfo, !GenMap, !Specs) :-
    proc_info_get_eval_method(ProcInfo0, EvalMethod),
    (
        EvalMethod = eval_tabled(TabledMethod),
        table_gen_transform_proc_if_possible(TabledMethod, PredId,
            ProcId, ProcInfo0, _, PredInfo0, _, !ModuleInfo, !GenMap, !Specs)
    ;
        EvalMethod = eval_normal,
        ( if
            TraceTableIO = yes,
            proc_info_has_io_state_pair(!.ModuleInfo, ProcInfo0,
                _InArgNum, _OutArgNum)
        then
            table_gen_process_io_proc(PredId, ProcId, ProcInfo0, PredInfo0,
                !ModuleInfo, !GenMap, !Specs)
        else
            true
        )
    ).

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

:- pred table_gen_process_io_proc(pred_id::in, proc_id::in, proc_info::in,
    pred_info::in, module_info::in, module_info::out,
    generator_map::in, generator_map::out,
    list(error_spec)::in, list(error_spec)::out) is det.

table_gen_process_io_proc(PredId, ProcId, ProcInfo0, PredInfo0,
        !ModuleInfo, !GenMap, !Specs) :-
    CodeModel = proc_info_interface_code_model(ProcInfo0),
    (
        CodeModel = model_det
    ;
        ( CodeModel = model_semi
        ; CodeModel = model_non
        ),
        pred_id_to_int(PredId, PredIdInt),
        Msg = string.format("I/O procedure pred id %d not model_det",
            [i(PredIdInt)]),
        unexpected($pred, Msg)
    ),
    module_info_get_globals(!.ModuleInfo, Globals),
    globals.lookup_bool_option(Globals, trace_table_io_all, TransformAll),
    globals.lookup_bool_option(Globals, trace_table_io_require, Require),
    proc_info_get_goal(ProcInfo0, BodyGoal),
    PredModuleName = predicate_module(!.ModuleInfo, PredId),
    should_io_procedure_be_transformed(TransformAll, Require, BodyGoal,
        PredModuleName, AnnotationIsMissing, TransformPrimitive),
    (
        AnnotationIsMissing = yes,
        Spec = report_missing_tabled_for_io(!.ModuleInfo, PredInfo0,
            PredId, ProcId),
        !:Specs = [Spec | !.Specs]
    ;
        AnnotationIsMissing = no
    ),
    (
        TransformPrimitive = no
    ;
        TransformPrimitive = yes(Unitize),
        globals.lookup_bool_option(Globals, trace_table_io_only_retry,
            TraceTableIoOnlyRetry),
        (
            TraceTableIoOnlyRetry = no,
            pred_info_get_class_context(PredInfo0, TypeClassConstraints),
            TypeClassConstraints =
                univ_exist_constraints(UnivConstraints, ExistConstraints),
            ( if
                UnivConstraints = [],
                ExistConstraints = []
            then
                EntryKind = entry_stores_procid_inputs_outputs
            else
                EntryKind = entry_stores_procid_outputs
            )
        ;
            TraceTableIoOnlyRetry = yes,
            EntryKind = entry_stores_outputs
        ),
        TableIoMethod = tabled_io(EntryKind, Unitize),
        proc_info_set_eval_method(eval_tabled(TableIoMethod),
            ProcInfo0, ProcInfo1),
        table_gen_transform_proc_if_possible(TableIoMethod,
            PredId, ProcId, ProcInfo1, _, PredInfo0, _, !ModuleInfo,
            !GenMap, !Specs)
    ).

:- pred should_io_procedure_be_transformed(bool::in, bool::in, hlds_goal::in,
    sym_name::in, bool::out, maybe(table_io_is_unitize)::out) is det.

should_io_procedure_be_transformed(TransformAll, Require, BodyGoal,
        PredModuleName, AnnotationIsMissing, TransformInfo) :-
    tabled_for_io_attributes(BodyGoal, TabledForIoAttrs),
    (
        TabledForIoAttrs = [],
        AnnotationIsMissing = no,
        TransformInfo = no
    ;
        TabledForIoAttrs = [TabledForIoAttr],
        (
            TabledForIoAttr = proc_not_tabled_for_io,
            ( if
                Require = yes,
                not any_mercury_builtin_module(PredModuleName)
            then
                AnnotationIsMissing = yes,
                TransformInfo = no
            else
                AnnotationIsMissing = no,
                (
                    TransformAll = no,
                    TransformInfo = no
                ;
                    TransformAll = yes,
                    may_call_mercury_attributes(BodyGoal, MayCallMercuryAttrs),
                    ( if MayCallMercuryAttrs = [proc_may_call_mercury] then
                        TransformInfo = no
                    else
                        TransformInfo = yes(table_io_alone)
                    )
                )
            )
        ;
            TabledForIoAttr = proc_tabled_for_descendant_io,
            AnnotationIsMissing = no,
            % The procedure itself doesn't do any I/O, so don't transform it.
            TransformInfo = no
        ;
            TabledForIoAttr = proc_tabled_for_io,
            AnnotationIsMissing = no,
            TransformInfo = yes(table_io_alone)
        ;
            TabledForIoAttr = proc_tabled_for_io_unitize,
            AnnotationIsMissing = no,
            TransformInfo = yes(table_io_unitize)
        )
    ;
        TabledForIoAttrs = [_, _ | _],
        % Since table_gen is run before inlining, each procedure
        % should contain at most one foreign_proc goal.
        unexpected($pred,
            "different tabled_for_io attributes in one procedure")
    ).

:- pred may_call_mercury_attributes(hlds_goal::in,
    list(proc_may_call_mercury)::out) is det.

may_call_mercury_attributes(Goal, MayCallMercuryAttrs) :-
    solutions.solutions(subgoal_may_call_mercury_attribute(Goal),
        MayCallMercuryAttrs).

:- pred subgoal_may_call_mercury_attribute(hlds_goal::in,
    proc_may_call_mercury::out) is nondet.

subgoal_may_call_mercury_attribute(Goal, MayCallMercuryAttr) :-
    some [SubGoal, Attrs] (
        goal_contains_goal(Goal, SubGoal),
        SubGoal = hlds_goal(call_foreign_proc(Attrs, _, _, _, _, _, _), _),
        MayCallMercuryAttr = get_may_call_mercury(Attrs)
    ).

:- pred tabled_for_io_attributes(hlds_goal::in, list(proc_tabled_for_io)::out)
    is det.

tabled_for_io_attributes(Goal, TabledForIoAttrs) :-
    solutions.solutions(subgoal_tabled_for_io_attribute(Goal),
        TabledForIoAttrs).

:- pred subgoal_tabled_for_io_attribute(hlds_goal::in, proc_tabled_for_io::out)
    is nondet.

subgoal_tabled_for_io_attribute(Goal, TabledForIoAttr) :-
    some [SubGoal, Attrs] (
        goal_contains_goal(Goal, SubGoal),
        SubGoal = hlds_goal(call_foreign_proc(Attrs, _, _, _, _, _, _), _),
        TabledForIoAttr = get_tabled_for_io(Attrs),
        not TabledForIoAttr = proc_not_tabled_for_io
    ).

:- func report_missing_tabled_for_io(module_info, pred_info, pred_id, proc_id)
    = error_spec.

report_missing_tabled_for_io(ModuleInfo, PredInfo, PredId, ProcId) = Spec :-
    pred_info_get_context(PredInfo, Context),
    ProcPieces = describe_one_proc_name(ModuleInfo, yes(color_subject),
        should_not_module_qualify, proc(PredId, ProcId)),
    Pieces = [words("Error:")] ++ ProcPieces ++ [words("contains")] ++
        color_as_incorrect([words("untabled I/O primitive.")]) ++
        [nl],
    Spec = spec($pred, severity_error, phase_code_gen, Context, Pieces).

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

:- pred table_gen_transform_proc_if_possible(tabled_eval_method::in,
    pred_id::in, proc_id::in, proc_info::in, proc_info::out,
    pred_info::in, pred_info::out, module_info::in, module_info::out,
    generator_map::in, generator_map::out,
    list(error_spec)::in, list(error_spec)::out) is det.

table_gen_transform_proc_if_possible(TabledMethod, PredId, ProcId,
        !ProcInfo, !PredInfo, !ModuleInfo, !GenMap, !Specs) :-
    find_grade_problems_for_tabling(!.ModuleInfo, PredId, ProcId, TabledMethod,
        GradeSpecs),
    (
        GradeSpecs = [],
        table_gen_transform_proc(TabledMethod, PredId, ProcId,
            !ProcInfo, !PredInfo, !ModuleInfo, !GenMap)
    ;
        GradeSpecs = [_ | _],
        ( if TabledMethod = tabled_memo(table_attr_ignore_without_warning) then
            true
        else
            !:Specs = GradeSpecs ++ !.Specs
        ),
        % XXX We set the evaluation method to eval_normal here to prevent
        % problems in the ml code generator if we are compiling in a grade
        % that does not support tabling. (See ml_gen_maybe_add_table_var/6
        % in ml_code_gen.m for further details.)
        %
        % We do this here rather than when processing the tabling pragmas
        % (in add_pragma.m) so that we can still generate error messages
        % for misuses of the tabling pragmas.
        proc_info_set_eval_method(eval_normal, !ProcInfo),
        module_info_set_pred_proc_info(PredId, ProcId, !.PredInfo,
            !.ProcInfo, !ModuleInfo)
    ).

:- pred find_grade_problems_for_tabling(module_info::in,
    pred_id::in, proc_id::in, tabled_eval_method::in,
    list(error_spec)::out) is det.

find_grade_problems_for_tabling(ModuleInfo, PredId, ProcId, TabledMethod,
        !:Specs) :-
    % We use severity_informational for any messages because severity_warning
    % would combine with --halt-at-warn to prevent the clean compilation
    % of the library and the compiler.
    %
    % Please keep this code in sync with current_grade_supports_tabling
    % in globals.m.
    !:Specs = [],
    module_info_get_globals(ModuleInfo, Globals),
    globals.get_target(Globals, Target),
    (
        Target = target_c
    ;
        ( Target = target_csharp
        ; Target = target_java
        ),
        general_cannot_table_reason_spec(ModuleInfo, PredId, ProcId,
            TabledMethod, gen_reason_non_c_backend, TargetSpec),
        !:Specs = [TargetSpec | !.Specs]
    ),
    globals.get_gc_method(Globals, GC),
    (
        GC = gc_accurate,
        general_cannot_table_reason_spec(ModuleInfo, PredId, ProcId,
            TabledMethod, gen_reason_gc_accurate, GcSpec),
        !:Specs = [GcSpec | !.Specs]
    ;
        GC = gc_hgc,
        general_cannot_table_reason_spec(ModuleInfo, PredId, ProcId,
            TabledMethod, gen_reason_gc_hgc, GcSpec),
        !:Specs = [GcSpec | !.Specs]
    ;
        ( GC = gc_automatic
        ; GC = gc_none
        ; GC = gc_boehm
        ; GC = gc_boehm_debug
        )
    ),
    globals.lookup_bool_option(Globals, parallel, Parallel),
    (
        Parallel = no
    ;
        Parallel = yes,
        general_cannot_table_reason_spec(ModuleInfo, PredId, ProcId,
            TabledMethod, gen_reason_parallel, ParSpec),
        !:Specs = [ParSpec | !.Specs]
    ),
    (
        TabledMethod = tabled_minimal(_),
        globals.lookup_bool_option(Globals, highlevel_code, HighLevelCode),
        (
            HighLevelCode = yes,
            mm_cannot_table_reason_spec(mm_reason_hlc, HLCSpec),
            !:Specs = [HLCSpec | !.Specs]
        ;
            HighLevelCode = no
        ),
        globals.lookup_bool_option(Globals, use_trail, UseTrail),
        (
            UseTrail = yes,
            mm_cannot_table_reason_spec(mm_reason_trailing, TrailSpec),
            !:Specs = [TrailSpec | !.Specs]
        ;
            UseTrail = no
        ),
        globals.lookup_bool_option(Globals, profile_calls, ProfileCalls),
        globals.lookup_bool_option(Globals, profile_deep, ProfileDeep),
        ( if ( ProfileCalls = yes ; ProfileDeep = yes ) then
            mm_cannot_table_reason_spec(mm_reason_profiling, ProfSpec),
            !:Specs = [ProfSpec | !.Specs]
        else
            true
        )
    ;
        ( TabledMethod = tabled_loop_check
        ; TabledMethod = tabled_memo(_)
        ; TabledMethod = tabled_io(_, _)
        )
    ).

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

:- pred general_cannot_table_reason_spec(module_info::in,
    pred_id::in, proc_id::in, tabled_eval_method::in,
    general_cannot_table_reason::in, error_spec::out) is det.

general_cannot_table_reason_spec(ModuleInfo, PredId, ProcId, TabledMethod,
        Reason, Spec) :-
    ReasonDesc = color_as_incorrect(gen_cannot_table_reason_desc(Reason)),
    (
        ( TabledMethod = tabled_loop_check
        ; TabledMethod = tabled_memo(_)
        ),
        TabledMethodStr = tabled_eval_method_to_string(TabledMethod),
        module_info_pred_info(ModuleInfo, PredId, PredInfo),
        ProcPieces = describe_qual_proc_name(ModuleInfo, proc(PredId, ProcId)),
        pred_info_get_context(PredInfo, Context),
        Pieces = [words("Ignoring the"), pragma_decl(TabledMethodStr),
            words("declaration for")] ++ ProcPieces ++ [suffix(","),
            words("because tabling is")] ++ ReasonDesc ++ [nl],
        Spec = spec($pred, severity_informational, phase_code_gen,
            Context, Pieces)
    ;
        TabledMethod = tabled_io(_, _),
        module_info_pred_info(ModuleInfo, PredId, PredInfo),
        pred_info_get_context(PredInfo, Context),
        Pieces = [words("Warning: debugging implicitly tables"),
            words("all predicates that perform I/O"),
            words("(to make the mdb command `retry' safe across I/O),"),
            words("but tabling is")] ++ ReasonDesc ++ [nl],
        Spec = spec($pred, severity_informational, phase_code_gen,
            Context, Pieces)
    ;
        TabledMethod = tabled_minimal(_),
        Pieces = [words("Error: minimal model tabling is")] ++
            ReasonDesc ++ [nl],
        % We generate one no-context error_spec for each affected predicate,
        % but we print only one copy of each duplicated error_spec.
        Spec = no_ctxt_spec($pred, severity_error, phase_code_gen, Pieces)
    ).

:- type general_cannot_table_reason
    --->    gen_reason_non_c_backend
    ;       gen_reason_gc_accurate
    ;       gen_reason_gc_hgc
    ;       gen_reason_parallel.

:- func gen_cannot_table_reason_desc(general_cannot_table_reason)
    = list(format_piece).

gen_cannot_table_reason_desc(Reason) = Desc :-
    (
        Reason = gen_reason_non_c_backend,
        Desc = [words("is implemented only on the C backend."), nl]
    ;
        Reason = gen_reason_gc_accurate,
        Desc = [words("is not compatible with --gc accurate."), nl]
    ;
        Reason = gen_reason_gc_hgc,
        Desc = [words("is not compatible with --gc hgc."), nl]
    ;
        Reason = gen_reason_parallel,
        Desc = [words("is not compatible with parallel execution."), nl]
    ).

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

:- pred mm_cannot_table_reason_spec(mm_cannot_table_reason::in,
    error_spec::out) is det.

mm_cannot_table_reason_spec(Reason, Spec) :-
    Pieces = [words("Error: minimal model tabling is not compatible with")] ++
        mm_cannot_table_reason_desc(Reason),
    % We generate one no-context error_spec for each affected predicate,
    % but we print only one copy of each duplicated error_spec.
    Spec = no_ctxt_spec($pred, severity_error, phase_code_gen, Pieces).

:- type mm_cannot_table_reason
    --->    mm_reason_hlc
    ;       mm_reason_trailing
    ;       mm_reason_profiling.

:- func mm_cannot_table_reason_desc(mm_cannot_table_reason)
    = list(format_piece).

mm_cannot_table_reason_desc(Reason) = Desc :-
    (
        Reason = mm_reason_hlc,
        Desc = [words("generating high level code.")]
    ;
        Reason = mm_reason_trailing,
        Desc = [words("trailing.")]
    ;
        Reason = mm_reason_profiling,
        Desc = [words("profiling.")]
    ).

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

:- pred table_gen_transform_proc(tabled_eval_method::in,
    pred_id::in, proc_id::in,
    proc_info::in, proc_info::out, pred_info::in, pred_info::out,
    module_info::in, module_info::out,
    generator_map::in, generator_map::out) is det.

table_gen_transform_proc(TabledMethod, PredId, ProcId, !ProcInfo, !PredInfo,
        !ModuleInfo, !GenMap) :-
    table_info_init(!.ModuleInfo, !.PredInfo, !.ProcInfo, TableInfo0),

    % Grab the appropriate fields from the pred_info and proc_info.
    proc_info_interface_determinism(!.ProcInfo, Detism),
    determinism_to_code_model(Detism, CodeModel),
    proc_info_get_headvars(!.ProcInfo, HeadVars),
    proc_info_get_var_table(!.ProcInfo, VarTable0),
    proc_info_get_goal(!.ProcInfo, OrigGoal),
    proc_info_get_argmodes(!.ProcInfo, ArgModes),
    proc_info_get_table_attributes(!.ProcInfo, MaybeAttributes),
    (
        MaybeAttributes = yes(Attributes)
    ;
        MaybeAttributes = no,
        Attributes = default_memo_table_attributes
    ),
    (
        TabledMethod = tabled_io(_, _),
        expect(unify(MaybeAttributes, no), $pred, "tabled_io and Attributes"),
        % Since we don't actually create a call table for I/O tabled
        % procedures, the value of MaybeSpecMethod doesn't really matter.
        MaybeSpecMethod = msm_all_same(arg_value),
        Statistics = table_do_not_gather_statistics,
        MaybeSizeLimit = no
    ;
        ( TabledMethod = tabled_loop_check
        ; TabledMethod = tabled_memo(_)
        ; TabledMethod = tabled_minimal(_)
        ),
        CallStrictness = Attributes ^ table_attr_strictness,
        Statistics = Attributes ^ table_attr_statistics,
        MaybeSizeLimit = Attributes ^ table_attr_size_limit,
        (
            CallStrictness = cts_all_strict,
            MaybeSpecMethod = msm_all_same(arg_value)
        ;
            CallStrictness = cts_all_fast_loose,
            MaybeSpecMethod = msm_all_same(arg_addr)
        ;
            CallStrictness = cts_specified(ArgMethods, HiddenArgMethod),
            MaybeSpecMethod = msm_specified(ArgMethods, HiddenArgMethod)
        ),
        (
            TabledMethod = tabled_loop_check
        ;
            TabledMethod = tabled_memo(_)
        ;
            TabledMethod = tabled_minimal(_),
            expect(unify(MaybeSizeLimit, no), $pred,
                "tabled_minimal with size limit"),
            expect(unify(MaybeSpecMethod, msm_all_same(arg_value)), $pred,
                "tabled_minimal without all_strict")
        )
    ),
    get_input_output_vars(!.ModuleInfo, VarTable0, HeadVars, ArgModes,
        MaybeSpecMethod, _, InputVarModeMethods, OutputVarModeMethods),
    allocate_slot_numbers(InputVarModeMethods, 0, NumberedInputVars),
    allocate_slot_numbers(OutputVarModeMethods, 0, NumberedOutputVars),
    (
        TabledMethod = tabled_io(Decl, Unitize),
        module_info_get_globals(!.ModuleInfo, Globals),
        globals.lookup_bool_option(Globals, trace_table_io_states,
            TableIoStates),
        assoc_list.from_corresponding_lists(HeadVars, ArgModes, HeadVarModes),
        create_new_io_goal(OrigGoal, Decl, Unitize, TableIoStates,
            PredId, ProcId, HeadVarModes, NumberedInputVars,
            NumberedOutputVars, VarTable0, VarTable,
            TableInfo0, TableInfo, Goal, MaybeProcTableIOInfo),
        MaybeCallTableTip = no,
        MaybeProcTableStructInfo = no
    ;
        TabledMethod = tabled_loop_check,
        create_new_loop_goal(OrigGoal, Statistics,
            PredId, ProcId, HeadVars, NumberedInputVars, NumberedOutputVars,
            VarTable0, VarTable, TableInfo0, TableInfo,
            CallTableTip, Goal, InputSteps),
        MaybeOutputSteps = no,
        generate_gen_proc_table_info(TableInfo, PredId, ProcId,
            VarTable, TabledMethod, InputSteps, MaybeOutputSteps,
            InputVarModeMethods, OutputVarModeMethods, ProcTableStructInfo),
        MaybeCallTableTip = yes(CallTableTip),
        MaybeProcTableIOInfo = no,
        MaybeProcTableStructInfo = yes(ProcTableStructInfo)
    ;
        TabledMethod = tabled_memo(_),
        (
            CodeModel = model_non,
            create_new_memo_non_goal(Detism, OrigGoal, Statistics,
                MaybeSizeLimit, PredId, ProcId,
                HeadVars, NumberedInputVars, NumberedOutputVars,
                VarTable0, VarTable, TableInfo0, TableInfo,
                CallTableTip, Goal, InputSteps, OutputSteps),
            MaybeOutputSteps = yes(OutputSteps)
        ;
            ( CodeModel = model_det
            ; CodeModel = model_semi
            ),
            create_new_memo_goal(Detism, OrigGoal, Statistics, MaybeSizeLimit,
                PredId, ProcId,
                HeadVars, NumberedInputVars, NumberedOutputVars,
                VarTable0, VarTable, TableInfo0, TableInfo,
                CallTableTip, Goal, InputSteps),
            MaybeOutputSteps = no
        ),
        generate_gen_proc_table_info(TableInfo, PredId, ProcId, VarTable,
            TabledMethod, InputSteps, MaybeOutputSteps,
            InputVarModeMethods, OutputVarModeMethods, ProcTableStructInfo),
        MaybeCallTableTip = yes(CallTableTip),
        MaybeProcTableIOInfo = no,
        MaybeProcTableStructInfo = yes(ProcTableStructInfo)
    ;
        TabledMethod = tabled_minimal(MinimalMethod),
        expect(unify(CodeModel, model_non), $pred,
            "table_gen_transform_proc: minimal model but not model_non"),
        (
            MinimalMethod = stack_copy,
            create_new_mm_goal(Detism, OrigGoal, Statistics, PredId, ProcId,
                HeadVars, NumberedInputVars, NumberedOutputVars,
                VarTable0, VarTable, TableInfo0, TableInfo,
                CallTableTip, Goal, InputSteps, OutputSteps),
            MaybeCallTableTip = yes(CallTableTip),
            MaybeOutputSteps = yes(OutputSteps),
            generate_gen_proc_table_info(TableInfo, PredId, ProcId, VarTable,
                TabledMethod, InputSteps, MaybeOutputSteps,
                InputVarModeMethods, OutputVarModeMethods,
                ProcTableStructInfo),
            MaybeProcTableStructInfo = yes(ProcTableStructInfo)
        ;
            MinimalMethod = own_stacks_consumer,
            do_own_stack_transform(Detism, OrigGoal, Statistics,
                PredId, ProcId, !.PredInfo, !.ProcInfo,
                HeadVars, NumberedInputVars, NumberedOutputVars,
                VarTable0, VarTable, TableInfo0, TableInfo,
                !GenMap, Goal, _InputSteps, _OutputSteps),
            MaybeCallTableTip = no,
            MaybeProcTableStructInfo = no
        ;
            MinimalMethod = own_stacks_generator,
            % The own_stacks_generator minimal_method is only ever introduced
            % by the transformation in this module; a procedure that hasn't
            % been transformed yet should not have this eval_method.
            unexpected($pred, "own stacks generator")
        ),
        MaybeProcTableIOInfo = no
    ),

    table_info_extract(TableInfo, !:ModuleInfo, !:PredInfo, !:ProcInfo),

    % Set the new values of the fields in proc_info and pred_info
    % and save in the module info.
    proc_info_set_goal(Goal, !ProcInfo),
    proc_info_set_var_table(VarTable, !ProcInfo),
    proc_info_set_call_table_tip(MaybeCallTableTip, !ProcInfo),

    (
        MaybeProcTableIOInfo = no
    ;
        MaybeProcTableIOInfo = yes(FinalProcTableIOInfo),
        proc_info_set_maybe_proc_table_io_info(yes(FinalProcTableIOInfo),
            !ProcInfo)
    ),

    (
        MaybeProcTableStructInfo = no
    ;
        MaybeProcTableStructInfo = yes(FinalProcTableStructInfo),
        PredProcId = proc(PredId, ProcId),
        add_proc_table_struct(PredProcId, FinalProcTableStructInfo, !.ProcInfo,
            !ModuleInfo)
    ),

    % Some of the instmap_deltas generated in this module are pretty dodgy
    % (especially those for if-then-elses), so recompute them here.
    % XXX Fix this: generate correct-by-construction instmap_deltas.
    recompute_instmap_delta_proc(no_recomp_atomics, !ProcInfo, !ModuleInfo),

    pred_info_get_proc_table(!.PredInfo, ProcTable1),
    map.det_update(ProcId, !.ProcInfo, ProcTable1, ProcTable),
    pred_info_set_proc_table(ProcTable, !PredInfo),

    % The transformation doesn't pay attention to the purity of compound goals,
    % so recompute the purity here.
    % XXX Fix this: generate correct-by-construction purity information.
    repuritycheck_proc(!.ModuleInfo, proc(PredId, ProcId), !PredInfo),
    module_info_set_pred_info(PredId, !.PredInfo, !ModuleInfo).

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

% Example of transformation for model_det loopcheck:
%
% :- pred p(int::in, int::out) is semidet.
%
% p(A, B) :-
%   <original code>.
%
% The transformed code would be:
%
% p(A, B) :-
%   T0 = <table pointer for p/2>,
%   impure table_lookup_insert_int(T0, A, T),
%   impure table_loop_setup(T, Status),
%   (
%       Status = loop_active,
%       error("detected infinite recursion in ...")
%   ;
%       Status = loop_inactive,
%       % status has been changed to active by the setup predicate
%       <original code>,
%       impure table_loop_mark_as_inactive(T)
%   ).
%
% Example of transformation for model_semi loopcheck:
%
% :- pred p(int::in, int::out) is semidet.
%
% p(A, B) :-
%   <original code>.
%
% The transformed code would be:
%
% p(A, B) :-
%   T0 = <table pointer for p/2>,
%   impure table_lookup_insert_int(T0, A, T),
%   impure table_loop_setup(T, Status),
%   (
%       Status = loop_active,
%       error("detected infinite recursion in ...")
%   ;
%       Status = loop_inactive,
%       % status has been changed to active by the setup predicate
%       ( if
%           <original code>, with B replaced by C
%       then
%           B = C,
%           impure table_loop_mark_as_inactive(T)
%       else
%           impure table_loop_mark_as_inactive_and_fail(T),
%       )
%   ).
%
% Example of transformation for model_non loopcheck:
%
% :- pred p(int::in, int::out) is semidet.
%
% p(A, B) :-
%   <original code>.
%
% The transformed code would be:
%
% p(A, B) :-
%   T0 = <table pointer for p/2>,
%   impure table_lookup_insert_int(T0, A, T),
%   impure table_loop_setup(T, Status),
%   (
%       Status = loop_active,
%       error("detected infinite recursion in ...")
%   ;
%       Status = loop_inactive,
%       % status has been changed to active by the setup predicate
%       (
%           <original code>,
%           (
%               impure table_loop_mark_as_inactive(T)
%           ;
%               impure table_loop_mark_as_active_and_fail(T),
%               fail
%           )
%       ;
%           impure table_loop_mark_as_inactive_and_fail(T)
%       )
%   ).

:- pred create_new_loop_goal(hlds_goal::in,
    table_attr_statistics::in, pred_id::in, proc_id::in, list(prog_var)::in,
    list(var_mode_pos_method)::in, list(var_mode_pos_method)::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    prog_var::out, hlds_goal::out, list(table_step_desc)::out) is det.

create_new_loop_goal(OrigGoal, Statistics, PredId, ProcId,
        HeadVars, NumberedInputVars, NumberedOutputVars, !VarTable,
        !TableInfo, TableTipVar, Goal, Steps) :-
    % Even if the original goal doesn't use all of the headvars,
    % the code generated by the tabling transformation does,
    % so we need to compute the nonlocals from the headvars rather
    % than getting it from the nonlocals field in the original goal.
    set_of_var.list_to_set(HeadVars, OrigNonLocals),
    OrigGoal = hlds_goal(_, OrigGoalInfo),
    OrigInstMapDelta = goal_info_get_instmap_delta(OrigGoalInfo),
    Context = goal_info_get_context(OrigGoalInfo),

    ModuleInfo = !.TableInfo ^ table_module_info,
    generate_plain_call_table_lookup_goal(loop_status_type,
        "table_loop_setup_shortcut", "MR_tbl_loop_setup",
        NumberedInputVars, PredId, ProcId, Statistics, Context,
        !VarTable, !TableInfo, TableTipVar, StatusVar, LookUpGoal, Steps),

    generate_error_goal(!.TableInfo, Context, infinite_recursion_msg,
        !VarTable, ActiveGoal),

    TableTipArg = foreign_arg(TableTipVar,
        yes(foreign_arg_name_mode(cur_table_node_name, in_mode)),
        trie_node_type, bp_native_if_possible),

    MarkInactivePredName = "table_loop_mark_as_inactive",
    MarkInactiveMacroName = "MR_tbl_loop_mark_as_inactive",
    MarkInactiveFailPredName = "table_loop_mark_as_inactive_and_fail",
    MarkInactiveFailMacroName = "MR_tbl_loop_mark_as_inactive_and_fail",
    MarkActiveFailPredName = "table_loop_mark_as_active_and_fail",
    MarkActiveFailMacroName = "MR_tbl_loop_mark_as_active_and_fail",

    DebugArgStr = get_debug_arg_string(!.TableInfo),
    MarkInactiveCode = "\t" ++ MarkInactiveMacroName ++
        "(" ++ DebugArgStr ++ ", " ++ cur_table_node_name ++ ");\n",
    MarkInactiveFailCode = "\t" ++ MarkInactiveFailMacroName ++
        "(" ++ DebugArgStr ++ ", " ++ cur_table_node_name ++ ");\n",
    MarkActiveFailCode = "\t" ++ MarkActiveFailMacroName ++
        "(" ++ DebugArgStr ++ ", " ++ cur_table_node_name ++ ");\n",

    table_call_foreign_proc(ModuleInfo, MarkInactivePredName,
        [TableTipArg], [], instmap_delta_bind_no_var, detism_det,
        purity_impure, tabling_c_attributes_dupl, MarkInactiveCode,
        Context, MarkInactiveGoal),
    table_call_foreign_proc(ModuleInfo, MarkInactiveFailPredName,
        [TableTipArg], [], instmap_delta_bind_no_var, detism_failure,
        purity_impure, tabling_c_attributes_dupl, MarkInactiveFailCode,
        Context, MarkInactiveFailGoal),
    table_call_foreign_proc(ModuleInfo, MarkActiveFailPredName,
        [TableTipArg], [], instmap_delta_bind_no_var, detism_failure,
        purity_impure, tabling_c_attributes_dupl, MarkActiveFailCode,
        Context, MarkActiveFailGoal),

    % The actual determinism of a predicate can be tighter than its
    % declared determinism. This can lead to problems, such as mantis bug 361.
    %
    % When we add code to the end of the procedure body to mark that the
    % current subgoal is now inactive, we keep the original procedure body's
    % determinism. This will cause a code generator abort (unexpected
    % determinism) if the "mark inactive" goal has a looser determinism
    % than the original body. This can happen by adding model_non "mark
    % inactive" code to the end of a model_det or model_semi goal,
    % or by adding model_semi "mark inactive" code to the end of a
    % model_det goal.
    %
    % We could fix this in one of two ways.
    %
    % - We can set the determinism of the compound goal that includes
    %   both the original body and the "mark inactive" goal based on the
    %   code model of the "mark inactive goal, which should always be
    %   the looser code model.
    %
    % - We can choose which transformation to apply based on the actual
    %   determinism of the procedure body, not its declared determinism.
    %
    % We implement the second approach.

    Detism = goal_info_get_determinism(OrigGoalInfo),
    determinism_to_code_model(Detism, CodeModel),
    set_of_var.list_to_set([TableTipVar | HeadVars], InactiveNonLocals),
    OutputVars = list.map(project_var, NumberedOutputVars),
    InactiveInstmapDelta = instmap_delta_bind_vars(OutputVars),
    (
        CodeModel = model_det,
        InactiveGoalExpr = conj(plain_conj, [OrigGoal, MarkInactiveGoal])
    ;
        CodeModel = model_semi,
        InstMapDelta = goal_info_get_instmap_delta(OrigGoalInfo),
        create_renaming(OutputVars, InstMapDelta, !VarTable,
            Unifies, NewVars, Renaming),
        rename_some_vars_in_goal(Renaming, OrigGoal, RenamedOrigGoal),

        ThenGoalExpr = conj(plain_conj, Unifies ++ [MarkInactiveGoal]),
        ThenVars = [TableTipVar | OutputVars] ++ NewVars,
        set_of_var.list_to_set(ThenVars, ThenNonLocals),
        goal_info_init_hide(ThenNonLocals, InactiveInstmapDelta, Detism,
            purity_impure, Context, ThenGoalInfo),
        ThenGoal = hlds_goal(ThenGoalExpr, ThenGoalInfo),

        InactiveGoalExpr = if_then_else([], RenamedOrigGoal,
            ThenGoal, MarkInactiveFailGoal)
    ;
        CodeModel = model_non,
        AfterGoalExpr = disj([MarkInactiveGoal, MarkActiveFailGoal]),
        instmap_delta_init_reachable(AfterInstMapDelta),
        goal_info_init_hide(set_of_var.make_singleton(TableTipVar),
            AfterInstMapDelta, detism_multi, purity_impure, Context,
            AfterGoalInfo),
        AfterGoal = hlds_goal(AfterGoalExpr, AfterGoalInfo),
        FirstGoalExpr = conj(plain_conj, [OrigGoal, AfterGoal]),
        OrigGINonLocals = goal_info_get_nonlocals(OrigGoalInfo),
        set_of_var.insert(TableTipVar, OrigGINonLocals, FirstNonlocals),
        goal_info_set_nonlocals(FirstNonlocals, OrigGoalInfo, FirstGoalInfo),
        FirstGoal = hlds_goal(FirstGoalExpr, FirstGoalInfo),
        InactiveGoalExpr = disj([FirstGoal, MarkInactiveFailGoal])
    ),
    goal_info_init_hide(InactiveNonLocals, InactiveInstmapDelta, Detism,
        purity_impure, Context, InactiveGoalInfo),
    InactiveGoal = hlds_goal(InactiveGoalExpr, InactiveGoalInfo),

    SwitchArms = [
        case(loop_active_cons_id, [], ActiveGoal),
        case(loop_inactive_cons_id, [], InactiveGoal)
    ],
    SwitchExpr = switch(StatusVar, cannot_fail, SwitchArms),
    set_of_var.insert_list([StatusVar, TableTipVar],
        InactiveNonLocals, SwitchNonLocals),
    goal_info_init_hide(SwitchNonLocals, InactiveInstmapDelta, Detism,
        purity_impure, Context, SwitchGoalInfo),
    SwitchGoal = hlds_goal(SwitchExpr, SwitchGoalInfo),

    GoalExpr = conj(plain_conj, [LookUpGoal, SwitchGoal]),
    goal_info_init_hide(OrigNonLocals, OrigInstMapDelta, Detism,
        purity_impure, Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

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

% Example of transformation for model_det memo:
%
% :- pred p(int::in, int::out) is semidet.
%
% p(A, B) :-
%   <original code>.
%
% The transformed code would be:
%
% p(A, B) :-
%   T0 = <table pointer for p/2>,
%   impure table_lookup_insert_int(T0, A, T),
%   impure table_memo_setup(T, Status),
%   (
%       Status = memo_det_succeeded,
%       semipure table_memo_get_answer_block(T, Block),
%       semipure table_restore_int_answer(Block, 0, B)
%   ;
%       Status = memo_det_active,
%       error("detected infinite recursion in ...")
%   ;
%       Status = memo_det_inactive,
%       % status has been changed to active by the setup predicate
%       <original code>
%       impure table_memo_create_answer_block(T, 1, Block),
%       impure table_save_int_answer(Block, 0, B)
%   ).
%
% Example of transformation for model_semi memo:
%
% :- pred p(int::in, int::out) is semidet.
%
% p(A, B) :-
%   <original code>.
%
% The transformed code would be:
%
% p(A, B) :-
%   T0 = <table pointer for p/2>,
%   impure table_lookup_insert_int(T0, A, T),
%   impure table_memo_setup(T, Status),
%   (
%       Status = memo_semi_failed,
%       fail
%   ;
%       Status = memo_semi_succeeded,
%       semipure table_memo_get_answer_block(T, Block),
%       semipure table_restore_int_answer(Block, 0, B)
%   ;
%       Status = memo_semi_active,
%       error("detected infinite recursion in ...")
%   ;
%       Status = memo_semi_inactive,
%       % status has been changed to active by the setup predicate
%       ( if
%           <original code>, with B replaced by C
%       then
%           B = C,
%           impure table_memo_create_answer_block(T, 1, Block),
%           impure table_save_int_answer(Block, 0, B)
%       else
%           impure table_memo_mark_as_failed(T),
%           fail
%       )
%   ).
%
% Example of transformation for model_non memo:
%
% :- pred p(int::in, int::out) is semidet.
%
% p(A, B) :-
%   <original code>.
%
% The transformed code would be:
%
% p(A, B) :-
%   CT0 = <table pointer for p/2>,
%   impure table_lookup_insert_int(CT0, A, CT1),
%   impure table_memo_non_setup(CT1, Record, Status),
%   (
%       Status = memo_non_complete,
%       semipure table_memo_return_all_nondet(T, Block),
%       semipure table_restore_int_answer(Block, 0, B)
%   ;
%       Status = memo_non_incomplete,
%       error("detected need for minimal model tabling in ...")
%   ;
%       Status = memo_non_active,
%       error("detected infinite recursion in ...")
%   ;
%       Status = memo_non_inactive,
%       (
%           <original code>,
%
%               % Check for duplicate answers.
%           semipure table_memo_get_answer_table(Record, AT0),
%           impure table_lookup_insert_int(AT0, B, AT1),
%               % Fail if the answer is already in the table;
%               % otherwise, put it into the table.
%           impure table_mm_answer_is_not_duplicate(AT1),
%
%               % Save the new answer in the table.
%           impure table_memo_create_answer_block(Record, 1, Block),
%           impure table_save_int_answer(Block, 0, B),
%           (
%               impure table_memo_mark_as_incomplete(R)
%           ;
%               impure table_memo_mark_as_active_and_fail(R),
%               fail
%           )
%       ;
%           impure table_memo_mark_as_complete_and_fail(R)
%       )
%   ).
%
% If there are no output variables, then instead of creating an answer block
% and filling it in, we call table_memo_mark_as_succeeded.

:- pred create_new_memo_goal(determinism::in, hlds_goal::in,
    table_attr_statistics::in, maybe(int)::in,
    pred_id::in, proc_id::in, list(prog_var)::in,
    list(var_mode_pos_method)::in, list(var_mode_pos_method)::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    prog_var::out, hlds_goal::out, list(table_step_desc)::out) is det.

create_new_memo_goal(Detism, OrigGoal, Statistics, _MaybeSizeLimit,
        PredId, ProcId, HeadVars, NumberedInputVars, NumberedOutputVars,
        !VarTable, !TableInfo, TableTipVar, Goal, Steps) :-
    % Even if the original goal doesn't use all of the headvars,
    % the code generated by the tabling transformation does,
    % so we need to compute the nonlocals from the headvars rather
    % than getting it from the nonlocals field in the original goal.
    set_of_var.list_to_set(HeadVars, OrigNonLocals),
    OrigGoal = hlds_goal(_, OrigGoalInfo),
    OrigInstMapDelta = goal_info_get_instmap_delta(OrigGoalInfo),
    Context = goal_info_get_context(OrigGoalInfo),

    ModuleInfo = !.TableInfo ^ table_module_info,
    determinism_to_code_model(Detism, CodeModel),
    (
        CodeModel = model_det,
        StatusType = memo_det_status_type,
        SetupPredName = "table_memo_det_setup_shortcut",
        SetupMacroName = "MR_tbl_memo_det_setup"
    ;
        CodeModel = model_semi,
        StatusType = memo_semi_status_type,
        SetupPredName = "table_memo_semi_setup_shortcut",
        SetupMacroName = "MR_tbl_memo_semi_setup"
    ;
        CodeModel = model_non,
        unexpected($pred, "model_non")
    ),
    generate_plain_call_table_lookup_goal(StatusType,
        SetupPredName, SetupMacroName, NumberedInputVars,
        PredId, ProcId, Statistics, Context, !VarTable,
        !TableInfo, TableTipVar, StatusVar, LookUpGoal, Steps),

    generate_error_goal(!.TableInfo, Context, infinite_recursion_msg,
        !VarTable, ActiveGoal),

    list.length(NumberedOutputVars, BlockSize),
    generate_memo_save_goal(NumberedOutputVars, TableTipVar, BlockSize,
        Context, !VarTable, !TableInfo, SaveAnswerGoals),
    generate_memo_restore_goal(NumberedOutputVars, OrigInstMapDelta,
        TableTipVar, Context, !VarTable, !.TableInfo, RestoreAnswerGoal),
    SucceededGoal = RestoreAnswerGoal,

    set_of_var.list_to_set([TableTipVar | HeadVars], InactiveNonLocals),
    OutputVars = list.map(project_var, NumberedOutputVars),
    InactiveInstmapDelta = instmap_delta_bind_vars(OutputVars),

    % The case CodeModel = model_non was caught by the code above.
    (
        CodeModel = model_det,
        InactiveGoalExpr = conj(plain_conj, [OrigGoal | SaveAnswerGoals]),
        goal_info_init_hide(InactiveNonLocals, InactiveInstmapDelta,
            Detism, purity_impure, Context, InactiveGoalInfo),
        InactiveGoal = hlds_goal(InactiveGoalExpr, InactiveGoalInfo),

        SwitchArms = [
            case(memo_det_active_cons_id, [], ActiveGoal),
            case(memo_det_inactive_cons_id, [], InactiveGoal),
            case(memo_det_succeeded_cons_id, [], SucceededGoal)
        ]
    ;
        CodeModel = model_semi,
        create_renaming(OutputVars, OrigInstMapDelta, !VarTable,
            Unifies, NewVars, Renaming),
        rename_some_vars_in_goal(Renaming, OrigGoal, RenamedOrigGoal),

        ThenGoalExpr = conj(plain_conj, Unifies ++ SaveAnswerGoals),
        ThenVars = [TableTipVar | OutputVars] ++ NewVars,
        set_of_var.list_to_set(ThenVars, ThenNonLocals),
        goal_info_init_hide(ThenNonLocals, InactiveInstmapDelta,
            detism_det, purity_impure, Context, ThenGoalInfo),
        ThenGoal = hlds_goal(ThenGoalExpr, ThenGoalInfo),

        MarkAsFailedPredName = "table_memo_mark_as_failed",
        MarkAsFailedMacroName = "MR_tbl_memo_mark_as_failed",
        TableTipArg = foreign_arg(TableTipVar,
            yes(foreign_arg_name_mode(cur_table_node_name, in_mode)),
            trie_node_type, bp_native_if_possible),
        DebugArgStr = get_debug_arg_string(!.TableInfo),
        MarkAsFailedCode = MarkAsFailedMacroName ++
            "(" ++ DebugArgStr ++ ", " ++ cur_table_node_name ++ ");",
        table_call_foreign_proc(ModuleInfo, MarkAsFailedPredName,
            [TableTipArg], [], instmap_delta_bind_no_var, detism_failure,
            purity_impure, tabling_c_attributes_dupl, MarkAsFailedCode,
            Context, ElseGoal),
        InactiveGoalExpr = if_then_else([], RenamedOrigGoal,
            ThenGoal, ElseGoal),
        goal_info_init_hide(InactiveNonLocals, InactiveInstmapDelta, Detism,
            purity_impure, Context, InactiveGoalInfo),
        InactiveGoal = hlds_goal(InactiveGoalExpr, InactiveGoalInfo),
        FailedGoal = fail_goal,

        SwitchArms = [
            case(memo_semi_active_cons_id, [], ActiveGoal),
            case(memo_semi_inactive_cons_id, [], InactiveGoal),
            case(memo_semi_succeeded_cons_id, [], SucceededGoal),
            case(memo_semi_failed_cons_id, [], FailedGoal)
        ]
    ),

    SwitchExpr = switch(StatusVar, cannot_fail, SwitchArms),
    set_of_var.insert(StatusVar, InactiveNonLocals, SwitchNonLocals),
    goal_info_init_hide(SwitchNonLocals, InactiveInstmapDelta,
        Detism, purity_impure, Context, SwitchGoalInfo),
    SwitchGoal = hlds_goal(SwitchExpr, SwitchGoalInfo),

    GoalExpr = conj(plain_conj, [LookUpGoal, SwitchGoal]),
    goal_info_init_hide(OrigNonLocals, OrigInstMapDelta, Detism, purity_impure,
        Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

:- pred create_new_memo_non_goal(determinism::in, hlds_goal::in,
    table_attr_statistics::in, maybe(int)::in,
    pred_id::in, proc_id::in, list(prog_var)::in,
    list(var_mode_pos_method)::in, list(var_mode_pos_method)::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    prog_var::out, hlds_goal::out,
    list(table_step_desc)::out, list(table_step_desc)::out) is det.

create_new_memo_non_goal(Detism, OrigGoal, Statistics, _MaybeSizeLimit,
        PredId, ProcId, HeadVars, NumberedInputVars, NumberedOutputVars,
        !VarTable, !TableInfo, RecordVar, Goal, InputSteps, OutputSteps) :-
    % Even if the original goal doesn't use all of the headvars,
    % the code generated by the tabling transformation does,
    % so we need to compute the nonlocals from the headvars rather
    % than getting it from the nonlocals field in the original goal.
    set_of_var.list_to_set(HeadVars, OrigNonLocals),
    OrigGoal = hlds_goal(_, OrigGoalInfo),
    OrigInstMapDelta = goal_info_get_instmap_delta(OrigGoalInfo),
    Context = goal_info_get_context(OrigGoalInfo),

    ModuleInfo = !.TableInfo ^ table_module_info,
    list.length(NumberedOutputVars, BlockSize),

    generate_error_goal(!.TableInfo, Context, infinite_recursion_msg,
        !VarTable, InfiniteRecursionGoal),
    generate_error_goal(!.TableInfo, Context, need_minimal_model_msg,
        !VarTable, NeedMinModelGoal),

    generate_memo_non_call_table_lookup_goal(NumberedInputVars,
        PredId, ProcId, Statistics, Context, !VarTable,
        !TableInfo, RecordVar, StatusVar, LookUpGoal, InputSteps),
    generate_memo_non_save_goals(NumberedOutputVars, PredId, ProcId,
        RecordVar, BlockSize, Statistics, Context, !VarTable,
        !TableInfo, OutputSteps, SaveAnswerGoals),

    generate_memo_non_restore_goal(Detism, NumberedOutputVars,
        OrigInstMapDelta, RecordVar, Context,
        !VarTable, !.TableInfo, RestoreAllAnswerGoal),

    RecordVarName = memo_non_record_name,
    RecordArg = foreign_arg(RecordVar,
        yes(foreign_arg_name_mode(RecordVarName, in_mode)),
        memo_non_record_type, bp_native_if_possible),

    MarkIncompletePredName = "table_memo_mark_as_incomplete",
    MarkIncompleteMacroName = "MR_tbl_memo_mark_as_incomplete",
    MarkActivePredName = "table_memo_mark_as_active_and_fail",
    MarkActiveMacroName = "MR_tbl_memo_mark_as_active_and_fail",
    MarkCompletePredName = "table_memo_mark_as_complete_and_fail",
    MarkCompleteMacroName = "MR_tbl_memo_mark_as_complete_and_fail",

    DebugArgStr = get_debug_arg_string(!.TableInfo),
    MarkIncompleteCode = MarkIncompleteMacroName ++
        "(" ++ DebugArgStr ++ ", " ++ RecordVarName ++ ");\n",
    MarkActiveCode = MarkActiveMacroName ++
        "(" ++ DebugArgStr ++ ", " ++ RecordVarName ++ ");\n",
    MarkCompleteCode = MarkCompleteMacroName ++
        "(" ++ DebugArgStr ++ ", " ++ RecordVarName ++ ");\n",

    table_call_foreign_proc(ModuleInfo, MarkIncompletePredName,
        [RecordArg], [], instmap_delta_bind_no_var, detism_det,
        purity_impure, tabling_c_attributes_dupl, MarkIncompleteCode,
        Context, MarkIncompleteGoal),
    table_call_foreign_proc(ModuleInfo, MarkActivePredName,
        [RecordArg], [], instmap_delta_bind_no_var, detism_failure,
        purity_impure, tabling_c_attributes_dupl, MarkActiveCode,
        Context, MarkActiveGoal),
    table_call_foreign_proc(ModuleInfo, MarkCompletePredName,
        [RecordArg], [], instmap_delta_bind_no_var, detism_failure,
        purity_impure, tabling_c_attributes_dupl, MarkCompleteCode,
        Context, MarkCompleteGoal),

    OrigSaveExpr = conj(plain_conj, [OrigGoal | SaveAnswerGoals]),
    set_of_var.insert(RecordVar, OrigNonLocals, OrigSaveNonLocals),
    create_instmap_delta([OrigGoal | SaveAnswerGoals], OrigSaveIMD0),
    instmap_delta_restrict(OrigSaveNonLocals, OrigSaveIMD0, OrigSaveIMD),
    goal_info_init_hide(OrigSaveNonLocals, OrigSaveIMD, detism_non,
        purity_impure, Context, OrigSaveGoalInfo),
    OrigSaveGoal = hlds_goal(OrigSaveExpr, OrigSaveGoalInfo),

    AfterExpr = disj([MarkIncompleteGoal, MarkActiveGoal]),
    AfterNonLocals = set_of_var.make_singleton(RecordVar),
    create_instmap_delta([], AfterInstMapDelta),
    goal_info_init_hide(AfterNonLocals, AfterInstMapDelta, detism_non,
        purity_impure, Context, AfterGoalInfo),
    AfterGoal = hlds_goal(AfterExpr, AfterGoalInfo),

    OrigSaveAfterExpr = conj(plain_conj, [OrigSaveGoal, AfterGoal]),
    OrigSaveAfterGoal = hlds_goal(OrigSaveAfterExpr, OrigSaveGoalInfo),

    InactiveExpr = disj([OrigSaveAfterGoal, MarkCompleteGoal]),
    InactiveGoal = hlds_goal(InactiveExpr, OrigSaveGoalInfo),

    set_of_var.list_to_set([RecordVar | HeadVars], InactiveNonLocals),
    OutputVars = list.map(project_var, NumberedOutputVars),
    InactiveInstmapDelta = instmap_delta_bind_vars(OutputVars),

    SwitchArms = [
        case(memo_non_active_cons_id, [], InfiniteRecursionGoal),
        case(memo_non_inactive_cons_id, [], InactiveGoal),
        case(memo_non_incomplete_cons_id, [], NeedMinModelGoal),
        case(memo_non_complete_cons_id, [], RestoreAllAnswerGoal)
    ],

    SwitchExpr = switch(StatusVar, cannot_fail, SwitchArms),
    set_of_var.insert(StatusVar, InactiveNonLocals, SwitchNonLocals),
    goal_info_init_hide(SwitchNonLocals, InactiveInstmapDelta, Detism,
        purity_impure, Context, SwitchGoalInfo),
    SwitchGoal = hlds_goal(SwitchExpr, SwitchGoalInfo),

    GoalExpr = conj(plain_conj, [LookUpGoal, SwitchGoal]),
    goal_info_init_hide(OrigNonLocals, OrigInstMapDelta, Detism, purity_impure,
        Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

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

% Example of transformation for tabling I/O, for I/O primitives (i.e.
% predicates defined by foreign_procs that take an input/output pair of
% io_state arguments) that have the tabled_for_io feature:
%
% :- pred p(int, string, io, io).
% :- mode p(in, out, di, uo) is det.
%
% p(A, B, S0, S) :-
%   <original code>
%
% The transformed code would be:
%
% p'(A, B, S0, S) :-
%   ( if
%       % Get the global I/O table, the global I/O counter,
%       % and the starting point for tabling I/O actions,
%       % if we are in the tabled range.
%       table_io_in_range(T0, Counter, Start)
%   then
%       % Look up the input arguments.
%       impure table_lookup_insert_start_int(T0, Counter, Start, T),
%       ( if semipure table_io_has_occurred(T) then
%           semipure table_memo_get_answer_block(T, Block),
%           impure table_restore_string_answer(Block, 0, B),
%           table_io_copy_io_state(S0, S)
%       else
%           <original code>
%           % Save the answers in the table.
%           impure table_io_create_answer_block(T, 1, Block),
%           impure table_save_string_answer(Block, 0, B)
%       )
%   else
%       <original code>
%   ).
%
% Note that copying the I/O state works only because variables of type
% io.state don't actually contain any information; the information is actually
% stored in global variables. However, if this ever changes, the transformation
% can be fixed simply by changing the value of --trace-table-io-states to yes,
% which will cause such values to be tabled along with the other output
% arguments.
%
% For I/O primitives that do not have tabled_for_io, we should require that
% they do not do any I/O in their own code, meaning that all their I/O is
% inside any Mercury code they call. We can then leave such primitives
% untransformed; the I/O primitives called from the inner Mercury engine
% will do the right thing. For now, this requirement is not enforced,
% which means that enabling I/O tabling (with --trace-table-io) does not
% guarantee that *all* I/O actions are tabled. This can cause inconsistent
% behavior after retry commands in mdb. This is the reason why retry across
% I/O is experimental for now.
%
% The reason why we require I/O primitives to be marked manually by a
% programmer with the tabled_for_io feature is to get the programmer to make
% sure that the primitive meets the requirement. Unfortunately, this cannot be
% automated, since automation would require analysis of arbitrary C code.
%
% The transformation for tabling I/O for declarative debugging is a variant
% of the usual transformation of I/O primitives. In this variant, the answer
% block contains not only the answers, but also a pointer to the primitive's
% proc layout structure and the values of the input aruments. The code we
% generate will fill in the slots containing this extra information before
% it executes the original goal.

:- pred create_new_io_goal(hlds_goal::in, table_io_entry_kind::in,
    table_io_is_unitize::in, bool::in, pred_id::in, proc_id::in,
    assoc_list(prog_var, mer_mode)::in,
    list(var_mode_pos_method)::in, list(var_mode_pos_method)::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    hlds_goal::out, maybe(proc_table_io_info)::out) is det.

create_new_io_goal(OrigGoal, TableIoEntryKind, Unitize, TableIoStates,
        PredId, ProcId, HeadVarModes, OrigInputVars, OrigOutputVars,
        !VarTable, !TableInfo, Goal, MaybeProcTableIOInfo) :-
    OrigGoal = hlds_goal(_, OrigGoalInfo),
    ModuleInfo0 = !.TableInfo ^ table_module_info,
    module_info_pred_info(ModuleInfo0, PredId, PredInfo),
    pred_info_get_markers(PredInfo, Markers),
    ( if check_marker(Markers, marker_user_marked_no_inline) then
        % If the predicate should not be inlined, then we create a new
        % predicate with the same body as the original predicate, which is
        % called wherever the original goal would appear in the transformed
        % code. This is necessary when the original goal is foreign C code
        % that uses labels. The original goal would otherwise be duplicated
        % by the transformation, resulting in duplicate label errors from
        % the C compiler.
        clone_proc_and_create_call(PredId, PredInfo, ProcId, CallExpr,
            ModuleInfo0, ModuleInfo),
        NewGoal = hlds_goal(CallExpr, OrigGoalInfo),
        !TableInfo ^ table_module_info := ModuleInfo
    else
        NewGoal = OrigGoal,
        ModuleInfo = ModuleInfo0
    ),
    OrigNonLocals = goal_info_get_nonlocals(OrigGoalInfo),
    Context = goal_info_get_context(OrigGoalInfo),

    (
        TableIoStates = yes,
        IoStateAssignToVars = [],
        IoStateAssignFromVars = [],
        SavedOutputVars = OrigOutputVars,
        SavedHeadVars = HeadVarModes
    ;
        TableIoStates = no,
        list.filter(var_mode_pos_is_io_state(!.VarTable),
            OrigOutputVars, IoStateAssignToVars, MisNumberedSavedOutputVars),
        reallocate_slot_numbers(MisNumberedSavedOutputVars, 0,
            SavedOutputVars),
        list.filter(var_mode_pos_is_io_state(!.VarTable),
            OrigInputVars, IoStateAssignFromVars, _MisNumberedSavedInputVars),
        list.filter(var_mode_is_io_state(!.VarTable),
            HeadVarModes, _, SavedHeadVars)
    ),
    generate_new_table_var("TableVar", trie_node_type, is_not_dummy_type,
        !VarTable, TableVar),
    generate_new_table_var("CounterVar", int_type, is_not_dummy_type,
        !VarTable, CounterVar),
    generate_new_table_var("StartVar", int_type, is_not_dummy_type,
        !VarTable, StartVar),
    table_plain_call(ModuleInfo, "table_io_in_range",
        [TableVar, CounterVar, StartVar],
        instmap_delta_bind_vars([TableVar, CounterVar, StartVar]),
        detism_semi, purity_impure, Context, InRangeGoal),
    generate_new_table_var("TipVar", trie_node_type, is_not_dummy_type,
        !VarTable, TipVar),
    table_plain_call(ModuleInfo, "table_lookup_insert_start_int",
        [TableVar, StartVar, CounterVar, TipVar],
        instmap_delta_bind_var(TipVar),
        detism_det, purity_impure, Context, LookupGoal),
    table_plain_call(ModuleInfo, "table_io_has_occurred",
        [TipVar], instmap_delta_bind_no_var,
        detism_semi, purity_impure, Context, OccurredGoal),
    (
        TableIoEntryKind = entry_stores_procid_inputs_outputs,
        ShroudedPredProcId = shroud_pred_proc_id(proc(PredId, ProcId)),
        TableIoEntryConsId = table_io_entry_desc(ShroudedPredProcId),
        make_const_construction_alloc(TableIoEntryConsId, c_pointer_type,
            is_not_dummy_type, "TableIoEntryDescPtr", TableIoEntryDescGoal,
            TableIoEntryDescPtrVar, !VarTable),
        allocate_plain_slot_numbers(SavedHeadVars, 1, NumberedSavedHeadVars),
        NumberedSaveVars = [
            var_mode_pos_method(TableIoEntryDescPtrVar, in_mode, 0, unit)
            | NumberedSavedHeadVars],
        UnnumberedSavedOutputVars = list.map(project_var, SavedOutputVars),
        list.filter(var_belong_to_list(UnnumberedSavedOutputVars),
            NumberedSaveVars, NumberedSavedOutputVars),
        NumberedRestoreVars = NumberedSavedOutputVars,

        ProcInfo0 = !.TableInfo ^ table_cur_proc_info,
        proc_info_get_rtti_varmaps(ProcInfo0, RttiVarMaps),
        continuation_info.generate_table_arg_type_info(!.VarTable, RttiVarMaps,
            list.map(project_var_pos, NumberedSavedHeadVars),
            TableArgTypeInfo),
        ProcTableIOInfo = proc_table_io_info(yes(TableArgTypeInfo)),
        MaybeProcTableIOInfo = yes(ProcTableIOInfo)
    ;
        TableIoEntryKind = entry_stores_procid_outputs,
        ShroudedPredProcId = shroud_pred_proc_id(proc(PredId, ProcId)),
        TableIoEntryConsId = table_io_entry_desc(ShroudedPredProcId),
        make_const_construction_alloc(TableIoEntryConsId, c_pointer_type,
            is_not_dummy_type, "TableIoEntryDescPtr", TableIoEntryDescGoal,
            TableIoEntryDescPtrVar, !VarTable),

        NumberedRestoreVars0 =
            list.map(project_out_arg_method, SavedOutputVars),
        reallocate_slot_numbers(NumberedRestoreVars0, 1,
            NumberedRestoreVars),
        NumberedSaveVars = [
            var_mode_pos_method(TableIoEntryDescPtrVar, in_mode, 0, unit)
            | NumberedRestoreVars],

        ProcTableIOInfo = proc_table_io_info(no),
        MaybeProcTableIOInfo = yes(ProcTableIOInfo)
    ;
        TableIoEntryKind = entry_stores_outputs,
        TableIoEntryDescGoal = true_goal,
        NumberedRestoreVars =
            list.map(project_out_arg_method, SavedOutputVars),
        NumberedSaveVars = NumberedRestoreVars,
        MaybeProcTableIOInfo = no
    ),
    list.length(NumberedSaveVars, BlockSize),
    OrigInstMapDelta = goal_info_get_instmap_delta(OrigGoalInfo),
    generate_memo_restore_goal(NumberedRestoreVars, OrigInstMapDelta, TipVar,
        Context, !VarTable, !.TableInfo, RestoreAnswerGoal0),
    (
        TableIoStates = yes,
        RestoreAnswerGoal = RestoreAnswerGoal0
    ;
        TableIoStates = no,
        ( if
            IoStateAssignFromVars = [IoStateAssignFromVarPrime],
            IoStateAssignToVars = [IoStateAssignToVarPrime]
        then
            IoStateAssignFromVar = project_var(IoStateAssignFromVarPrime),
            IoStateAssignToVar = project_var(IoStateAssignToVarPrime)
        else
            % The call to proc_info_has_io_state_pair in
            % table_gen_process_procs should ensure that we never get here.
            unexpected($pred, "one in / one out violation")
        ),
        table_plain_call(ModuleInfo, "table_io_copy_io_state",
            [IoStateAssignFromVar, IoStateAssignToVar],
            instmap_delta_from_assoc_list([
                IoStateAssignFromVar - ground(clobbered, none_or_default_func),
                IoStateAssignToVar - ground(unique, none_or_default_func)]),
            detism_det, purity_pure, Context, IoStateAssignGoal),
        RestoreAnswerGoalExpr = conj(plain_conj,
            [RestoreAnswerGoal0, IoStateAssignGoal]),
        create_instmap_delta([RestoreAnswerGoal0, IoStateAssignGoal],
            RestoreAnswerInstMapDelta0),
        RestoreAnswerGoal0 = hlds_goal(_, RestoreAnswerGoal0Info),
        RestoreAnswer0NonLocals =
            goal_info_get_nonlocals(RestoreAnswerGoal0Info),
        set_of_var.insert_list([IoStateAssignFromVar, IoStateAssignToVar],
            RestoreAnswer0NonLocals, RestoreAnswerNonLocals),
        instmap_delta_restrict(RestoreAnswerNonLocals,
            RestoreAnswerInstMapDelta0, RestoreAnswerInstMapDelta),
        goal_info_init_hide(RestoreAnswerNonLocals,
            RestoreAnswerInstMapDelta, detism_det, purity_semipure, Context,
            RestoreAnswerGoalInfo),
        RestoreAnswerGoal = hlds_goal(RestoreAnswerGoalExpr,
            RestoreAnswerGoalInfo)
    ),
    generate_memo_save_goal(NumberedSaveVars, TipVar, BlockSize,
        Context, !VarTable, !TableInfo, SaveAnswerGoals),
    (
        Unitize = table_io_alone,
        CallSaveAnswerGoalList =
            [NewGoal, TableIoEntryDescGoal | SaveAnswerGoals]
    ;
        Unitize = table_io_unitize,
        generate_new_table_var("SavedTraceEnabled", int_type,
            is_not_dummy_type, !VarTable, SavedTraceEnabledVar),
        table_plain_call(ModuleInfo, "table_io_left_bracket_unitized_goal",
            [SavedTraceEnabledVar],
            instmap_delta_bind_var(SavedTraceEnabledVar),
            detism_det, purity_impure, Context, LeftBracketGoal),
        table_plain_call(ModuleInfo, "table_io_right_bracket_unitized_goal",
            [SavedTraceEnabledVar], instmap_delta_bind_no_var,
            detism_det, purity_impure, Context, RightBracketGoal),
        CallSaveAnswerGoalList = [LeftBracketGoal, NewGoal,
            RightBracketGoal, TableIoEntryDescGoal | SaveAnswerGoals]
    ),
    CallSaveAnswerGoalExpr = conj(plain_conj, CallSaveAnswerGoalList),
    create_instmap_delta(CallSaveAnswerGoalList, CallSaveAnswerInstMapDelta0),
    set_of_var.insert(TipVar, OrigNonLocals, CallSaveAnswerNonLocals),
    instmap_delta_restrict(CallSaveAnswerNonLocals,
        CallSaveAnswerInstMapDelta0, CallSaveAnswerInstMapDelta),
    goal_info_init_hide(CallSaveAnswerNonLocals, CallSaveAnswerInstMapDelta,
        detism_det, purity_impure, Context, CallSaveAnswerGoalInfo0),
    goal_info_add_feature(feature_hide_debug_event,
        CallSaveAnswerGoalInfo0, CallSaveAnswerGoalInfo),
    CallSaveAnswerGoal = hlds_goal(CallSaveAnswerGoalExpr,
        CallSaveAnswerGoalInfo),

    GenIfNecGoalExpr = if_then_else([], OccurredGoal,
        RestoreAnswerGoal, CallSaveAnswerGoal),
    create_instmap_delta([OccurredGoal, RestoreAnswerGoal,
        CallSaveAnswerGoal], GenIfNecInstMapDelta0),
    set_of_var.insert(TipVar, OrigNonLocals, GenIfNecNonLocals),
    instmap_delta_restrict(GenIfNecNonLocals,
        GenIfNecInstMapDelta0, GenIfNecInstMapDelta),
    goal_info_init_hide(GenIfNecNonLocals, GenIfNecInstMapDelta, detism_det,
        purity_impure, Context, GenIfNecGoalInfo),
    GenIfNecGoal = hlds_goal(GenIfNecGoalExpr, GenIfNecGoalInfo),

    CheckAndGenAnswerGoalExpr = conj(plain_conj, [LookupGoal, GenIfNecGoal]),
    create_instmap_delta([LookupGoal, GenIfNecGoal],
        CheckAndGenAnswerInstMapDelta0),
    set_of_var.insert_list([TableVar, CounterVar, StartVar],
        OrigNonLocals, CheckAndGenAnswerNonLocals),
    instmap_delta_restrict(CheckAndGenAnswerNonLocals,
        CheckAndGenAnswerInstMapDelta0, CheckAndGenAnswerInstMapDelta),
    goal_info_init_hide(CheckAndGenAnswerNonLocals,
        CheckAndGenAnswerInstMapDelta, detism_det, purity_impure, Context,
        CheckAndGenAnswerGoalInfo),
    CheckAndGenAnswerGoal = hlds_goal(CheckAndGenAnswerGoalExpr,
        CheckAndGenAnswerGoalInfo),

    BodyGoalExpr = if_then_else([], InRangeGoal, CheckAndGenAnswerGoal,
        NewGoal),
    create_instmap_delta([InRangeGoal, CheckAndGenAnswerGoal, NewGoal],
        BodyInstMapDelta0),
    instmap_delta_restrict(OrigNonLocals, BodyInstMapDelta0, BodyInstMapDelta),
    goal_info_init_hide(OrigNonLocals, BodyInstMapDelta, detism_det,
        purity_impure, Context, BodyGoalInfo),
    Goal = hlds_goal(BodyGoalExpr, BodyGoalInfo).

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

% Example of transformation for nondet minimal_model:
%
% :- pred p(int, int).
% :- mode p(in, out) is nondet.
%
% p(A, B) :-
%   <original code>.
%
% The transformed code would be:
%
% p(A, B) :-
%       % Get a handle on the table.
%   CT0 = <table pointer for p/2>,
%
%       % Look up the input arguments, and set up the table.
%   impure table_lookup_insert_int(CT0, A, CT1),
%   impure table_mm_setup(CT1, Subgoal, Status),
%   (
%       Status = complete,
%           % Return all the answers from the complete table.
%       semipure table_mm_return_all_nondet(Subgoal, Block),
%       semipure table_restore_int_answer(Block, 0, B)
%   ;
%       Status = active,
%           % Suspend the current computational branch.
%           % Resume when the generator has computed some answers.
%       impure table_mm_suspend_consumer(Subgoal, Block),
%       semipure table_restore_int_answer(Block, 0, B)
%   ;
%       Status = inactive,
%       (
%           <original code>,
%
%               % Check for duplicate answers.
%           semipure table_mm_get_answer_table(Subgoal, AT0),
%           impure table_lookup_insert_int(AT0, B, AT1),
%               % Fail if the answer is already in the table;
%               % otherwise, put it into the table.
%           impure table_mm_answer_is_not_duplicate(AT1),
%
%               % Save the new answer in the table.
%           impure table_mm_create_answer_block(Subgoal, 1, Block),
%           impure table_save_int_answer(Block, 0, B)
%       ;
%               % Mark this subgoal as completely evaluated,
%               % modulo any dependencies on other subgoals.
%           impure table_mm_completion(Subgoal),
%           fail
%       )
%   ).

:- pred create_new_mm_goal(determinism::in, hlds_goal::in,
    table_attr_statistics::in, pred_id::in, proc_id::in, list(prog_var)::in,
    list(var_mode_pos_method)::in, list(var_mode_pos_method)::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    prog_var::out, hlds_goal::out,
    list(table_step_desc)::out, list(table_step_desc)::out) is det.

create_new_mm_goal(Detism, OrigGoal, Statistics, PredId, ProcId,
        HeadVars, NumberedInputVars, NumberedOutputVars, !VarTable,
        !TableInfo, SubgoalVar, Goal, InputSteps, OutputSteps) :-
    % Even if the original goal doesn't use all of the headvars,
    % the code generated by the tabling transformation does,
    % so we need to compute the nonlocals from the headvars rather
    % than getting it from the nonlocals field in the original goal.
    set_of_var.list_to_set(HeadVars, OrigNonLocals),
    OrigGoal = hlds_goal(_, OrigGoalInfo),
    OrigInstMapDelta = goal_info_get_instmap_delta(OrigGoalInfo),
    Context = goal_info_get_context(OrigGoalInfo),

    ModuleInfo = !.TableInfo ^ table_module_info,
    list.length(NumberedOutputVars, BlockSize),
    generate_mm_call_table_lookup_goal(NumberedInputVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo,
        SubgoalVar, StatusVar, LookUpGoal, InputSteps),
    generate_mm_save_goals(NumberedOutputVars, SubgoalVar, PredId, ProcId,
        BlockSize, Statistics, Context, !VarTable, !TableInfo,
        OutputSteps, SaveAnswerGoals),
    generate_mm_restore_goal(Detism, NumberedOutputVars, OrigInstMapDelta,
        SubgoalVar, Context, !VarTable, !.TableInfo,
        RestoreAllAnswerGoal),
    generate_mm_suspend_goal(NumberedOutputVars, OrigInstMapDelta,
        SubgoalVar, Context, !VarTable, !.TableInfo, SuspendGoal),

    MainExpr = conj(plain_conj, [OrigGoal | SaveAnswerGoals]),
    set_of_var.insert_list([SubgoalVar, StatusVar],
        OrigNonLocals, MainNonLocals),
    create_instmap_delta([OrigGoal | SaveAnswerGoals], MainIMD0),
    instmap_delta_restrict(MainNonLocals, MainIMD0, MainIMD),
    goal_info_init_hide(MainNonLocals, MainIMD, detism_non, purity_impure,
        Context, MainGoalInfo),
    MainGoal = hlds_goal(MainExpr, MainGoalInfo),

    table_plain_call(ModuleInfo, "table_mm_completion",
        [SubgoalVar], instmap_delta_bind_no_var,
        detism_det, purity_impure, Context, ResumeGoal0),
    append_fail(ResumeGoal0, ResumeGoal),
    InactiveExpr = disj([MainGoal, ResumeGoal]),
    InactiveGoal = hlds_goal(InactiveExpr, MainGoalInfo),

    SwitchArms = [
        case(mm_inactive_cons_id, [], InactiveGoal),
        case(mm_active_cons_id, [], SuspendGoal),
        case(mm_complete_cons_id, [], RestoreAllAnswerGoal)
    ],
    SwitchExpr = switch(StatusVar, cannot_fail, SwitchArms),
    goal_info_add_feature(feature_hide_debug_event,
        MainGoalInfo, SwitchGoalInfo),
    SwitchGoal = hlds_goal(SwitchExpr, SwitchGoalInfo),

    GoalExpr = conj(plain_conj, [LookUpGoal, SwitchGoal]),
    goal_info_init_hide(OrigNonLocals, OrigInstMapDelta, detism_non,
        purity_impure, Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

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

% Example of transformation for nondet minimal_model_own_stack:
%
% :- pred p(int, int).
% :- mode p(in, out) is nondet
%
% p(A, B) :- e(A, B).
% p(A, B) :- p(A, C), e(C, B).
%
% The transformed code would be something like this; see also forest.ps
% in the tabling directory in the papers CVS module.
%
% p2_gen(A, B) :-
%   impure table_mmos_pickup_generator(Generator),
%   (
%       (
%           %
%           % Original goals
%           %
%       ),
%
%       % Check for duplicate answers.
%       semipure table_mmos_get_answer_table(Generator, AT0),
%       impure table_lookup_insert_int(AT0, B, AT1),
%
%       % Fail if the answer is already in the table;
%       % otherwise, put it into the table.
%       impure table_mmos_answer_is_not_duplicate(AT1),
%
%       % Save the new answer in the table.
%       impure table_mmos_create_answer_block(Generator, 1, AnswerBlock),
%       impure table_save_int_ans(AnswerBlock, 0, B),
%   ;
%       impure table_mmos_completion(Generator)
%   ).
%   MR_succeed is replaced by table_mmos_return_answer(Generator).
%
% p(A, B) :-
%   CT0 = <table pointer for p/2>,
%   impure table_lookup_insert_int(CT0, A, CT1),
%   ( if CT1 = NULL then
%       table_mmos_save_inputs(0, A),   % into global var MR_mmos_input_arg
%       Generator = table_mmos_setup_generator(CT1, 1, p2_gen, "p"),
%   then
%       Generator = trie_node_to_generator(CT1)
%   ),
%   impure table_mmos_setup_consumer(Generator, Consumer),
%   impure table_mmos_consume_next_answer_nondet(Consumer, AnswerBlock),
%   impure table_restore_int_ans(AnswerBlock, 0, B).

:- pred do_own_stack_transform(determinism::in, hlds_goal::in,
    table_attr_statistics::in, pred_id::in, proc_id::in,
    pred_info::in, proc_info::in, list(prog_var)::in,
    list(var_mode_pos_method)::in, list(var_mode_pos_method)::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    generator_map::in, generator_map::out, hlds_goal::out,
    list(table_step_desc)::out, list(table_step_desc)::out) is det.

do_own_stack_transform(Detism, OrigGoal, Statistics, PredId, ProcId,
        PredInfo0, ProcInfo0, HeadVars, NumberedInputVars, NumberedOutputVars,
        !VarTable, !TableInfo, !GenMap, Goal, InputSteps, OutputSteps) :-
    PredName = pred_info_name(PredInfo0),
    ( if map.search(!.GenMap, PredId, GeneratorPredIdPrime) then
        GeneratorPredId = GeneratorPredIdPrime
    else
        clone_pred_info(PredId, ProcId, PredInfo0, HeadVars,
            NumberedOutputVars, GeneratorPredId, !TableInfo),
        map.det_insert(PredId, GeneratorPredId, !GenMap)
    ),

    % Even if the original goal doesn't use all of the headvars,
    % the code generated by the tabling transformation does,
    % so we need to compute the nonlocals from the headvars rather
    % than getting it from the nonlocals field in the original goal.
    set_of_var.list_to_set(HeadVars, OrigNonLocals),
    OrigGoal = hlds_goal(_, OrigGoalInfo),
    OrigInstMapDelta = goal_info_get_instmap_delta(OrigGoalInfo),
    Context = goal_info_get_context(OrigGoalInfo),

    list.length(NumberedInputVars, NumInputVars),
    ModuleInfo = !.TableInfo ^ table_module_info,

    % The type is a lie, but a safe one: the variable we create
    % is handled only by C code. The reason why we lie is that
    % creating the right type would be quite complicated.
    GeneratorPredType = c_pointer_type,

    generate_new_table_var("GeneratorPredVar", GeneratorPredType,
        is_not_dummy_type, !VarTable, GeneratorPredVar),
    generate_new_table_var("Consumer", consumer_type, is_not_dummy_type,
        !VarTable, ConsumerVar),

    ShroudedPredProcId = shroud_pred_proc_id(proc(GeneratorPredId, ProcId)),
    GeneratorConsId = closure_cons(ShroudedPredProcId),
    make_const_construction(Context, GeneratorPredVar, GeneratorConsId,
        MakeGeneratorVarGoal),

    generate_call_table_lookup_goals(NumberedInputVars,
        GeneratorPredId, ProcId, Statistics, Context, !VarTable, !TableInfo,
        InputSteps, _TableTipVar, _TableTipArg, InfoArg, LookupForeignArgs,
        LookupPrefixGoals, LookupCodeStr, _CallTableTipAssignStr),

    InputVarModes = list.map(project_mode, NumberedInputVars),
    assoc_list.from_corresponding_lists(LookupForeignArgs, InputVarModes,
        LookupForeignArgModes),
    generate_save_input_vars_code(LookupForeignArgModes, ModuleInfo, 0,
        PickupForeignArgs, SaveInputVarCode, PickupInputVarCode),

    GeneratorPredVarName = generator_pred_name,
    ConsumerVarName = consumer_name,

    GeneratorPredArg = foreign_arg(GeneratorPredVar,
        yes(foreign_arg_name_mode(generator_pred_name, in_mode)),
        GeneratorPredType, bp_native_if_possible),
    ConsumerArg = foreign_arg(ConsumerVar,
        yes(foreign_arg_name_mode(ConsumerVarName, out_mode)),
        consumer_type, bp_native_if_possible),

    LookupDeclCodeStr =
        "\tMR_TrieNode " ++ cur_table_node_name ++ ";\n" ++
        "\tMR_TrieNode " ++ next_table_node_name ++ ";\n" ++
        "\tMR_GeneratorPtr " ++ generator_name ++ ";\n\n" ++
        LookupCodeStr,

    SetupPredName = "table_mmos_setup_consumer",
    SetupCode = "\t" ++ generator_name ++ " = " ++
        cur_table_node_name ++ "->MR_generator;\n" ++
        "\tif (" ++ generator_name ++ " == NULL) {\n" ++
            SaveInputVarCode ++
            "\t\t" ++ generator_name ++ " = MR_table_mmos_setup_generator(" ++
                cur_table_node_name ++ ",\n\t\t\t"
                ++ int_to_string(NumInputVars) ++ ", "
                ++ GeneratorPredVarName ++ ", " ++
                """" ++ PredName ++ """);\n" ++
            "\t\tMR_mmos_new_generator = " ++ generator_name ++ ";\n" ++
        "\t}\n" ++
        "\t" ++ consumer_name ++ " = " ++
            "MR_table_mmos_setup_consumer(" ++ generator_name ++
            ", """ ++ PredName ++ """);\n",
    table_call_foreign_proc(ModuleInfo, SetupPredName,
        [InfoArg, GeneratorPredArg, ConsumerArg], LookupForeignArgs,
        instmap_delta_bind_var(ConsumerVar),
        detism_det, purity_impure, make_generator_c_attributes,
        LookupDeclCodeStr ++ SetupCode, Context, SetupGoal),
    % We don't attach the call_table_tip attribute to the setup goal, since
    % retrying across the creation of the generator should not require undoing
    % the creation of the generator. Of course, for this to work *properly*,
    % the runtime system and the debugger will need to cooperate to effectively
    % present to the user a distinct sequence of trace events for each context.
    % attach_call_table_tip(SetupGoal0, SetupGoal),
    LookupSetupGoals = [MakeGeneratorVarGoal | LookupPrefixGoals]
        ++ [SetupGoal],

    generate_new_table_var("AnswerBlock", answer_block_type, is_not_dummy_type,
        !VarTable, AnswerBlockVar),
    ( if Detism = detism_multi then
        ConsumePredName = "table_mmos_consume_next_answer_multi"
    else if Detism = detism_non then
        ConsumePredName = "table_mmos_consume_next_answer_nondet"
    else
        unexpected($pred, "invalid determinism")
    ),
    % XXX consider inlining the predicate being called
    table_plain_call(ModuleInfo, ConsumePredName,
        [ConsumerVar, AnswerBlockVar], instmap_delta_bind_var(AnswerBlockVar),
        Detism, purity_impure, Context, GetNextAnswerGoal),
    DebugArgStr = get_debug_arg_string(!.TableInfo),
    generate_restore_goals(ModuleInfo, OrigInstMapDelta, DebugArgStr,
        NumberedOutputVars, RestoreInstMapDeltaSrc,
        RestoreArgs, RestoreCodeStr, !VarTable),
    AnswerBlockArg = foreign_arg(AnswerBlockVar,
        yes(foreign_arg_name_mode(answer_block_name, in_mode)),
        answer_block_type, bp_native_if_possible),
    RestoreAllPredName = "table_mmos_restore_answers",
    table_call_foreign_proc(ModuleInfo, RestoreAllPredName,
        [AnswerBlockArg], RestoreArgs,
        instmap_delta_from_assoc_list(RestoreInstMapDeltaSrc),
        detism_det, purity_semipure, tabling_c_attributes_no_dupl,
        RestoreCodeStr, Context, RestoreGoal),

    GoalExpr = conj(plain_conj,
        LookupSetupGoals ++ [GetNextAnswerGoal, RestoreGoal]),
    goal_info_init(OrigNonLocals, OrigInstMapDelta, Detism, purity_impure,
        Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo),

    module_info_pred_info(ModuleInfo, GeneratorPredId, GeneratorPredInfo),
    table_info_init(ModuleInfo, GeneratorPredInfo, ProcInfo0,
        GeneratorTableInfo0),
    do_own_stack_create_generator(GeneratorPredId, ProcId, GeneratorPredInfo,
        ProcInfo0, Statistics, Context, GeneratorPredVar, DebugArgStr,
        PickupInputVarCode, PickupForeignArgs,
        NumberedInputVars, NumberedOutputVars,
        OrigNonLocals, OrigInstMapDelta, !.VarTable,
        GeneratorTableInfo0, GeneratorTableInfo, InputSteps, OutputSteps),
    !TableInfo ^ table_module_info := GeneratorTableInfo ^ table_module_info.

:- pred generate_save_input_vars_code(assoc_list(foreign_arg, mer_mode)::in,
    module_info::in, int::in, list(foreign_arg)::out, string::out, string::out)
    is det.

generate_save_input_vars_code([], _, _, [], "", "").
generate_save_input_vars_code([InputArg - Mode | InputArgModes], ModuleInfo,
        Pos, [PickupArg | PickupArgs], SaveVarCode ++ SaveVarCodes,
        PickupVarCode ++ PickupVarCodes) :-
    InputArg = foreign_arg(InputVar, MaybeArgNameMode, Type, _),
    (
        MaybeArgNameMode = yes(foreign_arg_name_mode(InputVarName, _InMode))
    ;
        MaybeArgNameMode = no,
        unexpected($pred, "no InputVarName")
    ),
    mode_get_insts(ModuleInfo, Mode, InitInst, _FinalInst),
    PickupMode = from_to_mode(free, InitInst),
    PickupArg = foreign_arg(InputVar,
        yes(foreign_arg_name_mode(InputVarName, PickupMode)),
        Type, bp_native_if_possible),
    SaveVarCode = "\t\tMR_table_mmos_save_input_arg(" ++
        int_to_string(Pos) ++ ", " ++ InputVarName ++ ");\n",
    PickupVarCode = "\t\tMR_table_mmos_pickup_input_arg(" ++
        int_to_string(Pos) ++ ", " ++ InputVarName ++ ");\n",
    generate_save_input_vars_code(InputArgModes, ModuleInfo, Pos + 1,
        PickupArgs, SaveVarCodes, PickupVarCodes).

:- pred do_own_stack_create_generator(pred_id::in, proc_id::in,
    pred_info::in, proc_info::in,
    table_attr_statistics::in, term.context::in,
    prog_var::in, string::in, string::in, list(foreign_arg)::in,
    list(var_mode_pos_method)::in, list(var_mode_pos_method)::in,
    set_of_progvar::in, instmap_delta::in,
    var_table::in, table_info::in, table_info::out,
    list(table_step_desc)::in, list(table_step_desc)::out) is det.

do_own_stack_create_generator(PredId, ProcId, !.PredInfo, !.ProcInfo,
        Statistics, Context, GeneratorVar, DebugArgStr, PickupVarCode,
        PickupForeignArgs, NumberedInputVars, NumberedOutputVars,
        OrigNonLocals, OrigInstMapDelta, !.VarTable, !TableInfo,
        InputSteps, OutputSteps) :-
    ModuleInfo0 = !.TableInfo ^ table_module_info,

    proc_info_set_headvars(list.map(project_var, NumberedOutputVars),
        !ProcInfo),
    proc_info_set_argmodes(list.map(project_mode, NumberedOutputVars),
        !ProcInfo),
    PickupInstMapDeltaSrc0 = list.map(project_var_init_inst(ModuleInfo0),
        NumberedInputVars),
    PickupInstMapDeltaSrc = [GeneratorVar - ground_inst
        | PickupInstMapDeltaSrc0],
    PickupGeneratorCode = "\t\t" ++ generator_name ++
        " = MR_mmos_new_generator;\n",
    PickupGeneratorArg = foreign_arg(GeneratorVar,
        yes(foreign_arg_name_mode(generator_name, out_mode)),
        generator_type, bp_native_if_possible),
    table_call_foreign_proc(ModuleInfo0, "table_mmos_pickup_inputs",
        [PickupGeneratorArg], PickupForeignArgs,
        instmap_delta_from_assoc_list(PickupInstMapDeltaSrc),
        detism_det, purity_impure, tabling_c_attributes_no_dupl,
        PickupGeneratorCode ++ PickupVarCode, Context, PickupGoal),

    list.length(NumberedOutputVars, BlockSize),
    generate_own_stack_save_return_goal(NumberedOutputVars, GeneratorVar,
        PredId, ProcId, BlockSize, Statistics, Context, !VarTable,
        !TableInfo, OutputSteps, SaveReturnAnswerGoals),

    proc_info_get_goal(!.ProcInfo, OrigGoal),
    OrigGoal = hlds_goal(_, OrigGoalInfo),

    MainGoalExpr = conj(plain_conj, [OrigGoal | SaveReturnAnswerGoals]),
    Detism = goal_info_get_determinism(OrigGoalInfo),
    set_of_var.insert(GeneratorVar, OrigNonLocals, NonLocals),
    goal_info_init(NonLocals, OrigInstMapDelta, Detism, purity_impure, Context,
        MainGoalInfo0),
    goal_info_add_feature(feature_hide_debug_event,
        MainGoalInfo0, MainGoalInfo),
    MainGoal = hlds_goal(MainGoalExpr, MainGoalInfo),

    CompletionCode = "\t\t" ++ "MR_tbl_mmos_completion(" ++
        DebugArgStr ++ ", " ++ generator_name ++ ");\n",
    CompletionArg = foreign_arg(GeneratorVar,
        yes(foreign_arg_name_mode(generator_name, in_mode)),
        generator_type, bp_native_if_possible),
    table_call_foreign_proc(ModuleInfo0, "table_mmos_completion",
        [CompletionArg], [], instmap_delta_bind_no_var,
        detism_failure, purity_impure, tabling_c_attributes_no_dupl,
        CompletionCode, Context, CompletionGoal),

    DisjGoalExpr = disj([MainGoal, CompletionGoal]),
    DisjGoal = hlds_goal(DisjGoalExpr, MainGoalInfo),

    GoalExpr = conj(plain_conj, [PickupGoal, DisjGoal]),
    Goal = hlds_goal(GoalExpr, OrigGoalInfo),
    proc_info_set_goal(Goal, !ProcInfo),

    proc_info_set_var_table(!.VarTable, !ProcInfo),

    GenTabledMethod = tabled_minimal(own_stacks_generator),
    InputVarModeMethods = list.map(project_out_pos, NumberedInputVars),
    OutputVarModeMethods = list.map(project_out_pos, NumberedOutputVars),
    generate_gen_proc_table_info(!.TableInfo, PredId, ProcId, !.VarTable,
        GenTabledMethod, InputSteps, yes(OutputSteps),
        InputVarModeMethods, OutputVarModeMethods, ProcTableStructInfo),

    PredProcId = proc(PredId, ProcId),
    add_proc_table_struct(PredProcId, ProcTableStructInfo, !.ProcInfo,
        ModuleInfo0, ModuleInfo1),

    SpecialReturn = generator_return(returning_generator_locn, DebugArgStr),
    proc_info_set_maybe_special_return(yes(SpecialReturn), !ProcInfo),
    proc_info_set_eval_method(eval_tabled(GenTabledMethod), !ProcInfo),

    pred_info_get_proc_table(!.PredInfo, ProcTable0),
    map.det_insert(ProcId, !.ProcInfo, ProcTable0, ProcTable),
    pred_info_set_proc_table(ProcTable, !PredInfo),

    module_info_set_pred_info(PredId, !.PredInfo, ModuleInfo1, ModuleInfo),
    !TableInfo ^ table_module_info := ModuleInfo.

:- pred clone_pred_info(pred_id::in, proc_id::in, pred_info::in,
    list(prog_var)::in, list(var_mode_pos_method)::in, pred_id::out,
    table_info::in, table_info::out) is det.

clone_pred_info(OrigPredId, OrigProcId, PredInfo0, HeadVars,
        NumberedOutputVars, GeneratorPredId, !TableInfo) :-
    % We don't have any procedures for the generator yet. We will copy
    % the consumers' procedures later, one by one, as they are transformed.

    ModuleName = pred_info_module(PredInfo0),
    PredOrFunc = pred_info_is_pred_or_func(PredInfo0),
    PredName = pred_info_name(PredInfo0),
    pred_info_get_context(PredInfo0, Context),
    % The generator is local even if the original predicate is exported.
    PredStatus = pred_status(status_local),
    pred_info_get_goal_type(PredInfo0, GoalType),
    pred_info_get_markers(PredInfo0, Markers0),
    pred_info_get_arg_types(PredInfo0, ArgTypes0),
    pred_info_get_typevarset(PredInfo0, TypeVarSet),
    pred_info_get_exist_quant_tvars(PredInfo0, ExistQVars),
    pred_info_get_class_context(PredInfo0, ClassContext),
    pred_info_get_constraint_proof_map(PredInfo0, ClassProofMap),
    pred_info_get_constraint_map(PredInfo0, ClassConstraintMap),
    pred_info_get_origin(PredInfo0, OrigOrigin),
    pred_info_get_clauses_info(PredInfo0, ClausesInfo),
    pred_info_get_var_name_remap(PredInfo0, VarNameRemap),

    Transform = tn_minimal_model_generator(PredOrFunc,
        proc_id_to_int(OrigProcId)),
    make_transformed_pred_name(PredName, Transform, GenPredName),
    assoc_list.from_corresponding_lists(HeadVars, ArgTypes0, HeadVarTypes),
    keep_only_output_arg_types(HeadVarTypes, NumberedOutputVars, ArgTypes),
    PredFormArity = arg_list_arity(ArgTypes),

    markers_to_marker_list(Markers0, MarkerList0),
    list.filter(filter_marker, MarkerList0, MarkerList),
    marker_list_to_markers(MarkerList, Markers),

    Origin = origin_pred_transform(pred_transform_table_generator,
        OrigOrigin, OrigPredId),
    CurUserDecl = maybe.no,
    pred_info_init(PredOrFunc, ModuleName, GenPredName, PredFormArity, Context,
        Origin, PredStatus, CurUserDecl, GoalType, Markers,
        ArgTypes, TypeVarSet, ExistQVars, ClassContext, ClassProofMap,
        ClassConstraintMap, ClausesInfo, VarNameRemap, GenPredInfo),

    ModuleInfo0 = !.TableInfo ^ table_module_info,
    module_info_get_predicate_table(ModuleInfo0, PredTable0),
    predicate_table_insert(GenPredInfo, GeneratorPredId,
        PredTable0, PredTable),
    module_info_set_predicate_table(PredTable, ModuleInfo0, ModuleInfo),
    !TableInfo ^ table_module_info := ModuleInfo.

    % clone_proc_and_create_call(PredInfo, ProcId, CallExpr, !ModuleInfo):
    %
    % This predicate creates a new procedure with the same body as the
    % procedure with ProcId in PredInfo. It then creates a call goal
    % which calls the new procedure with its formal arguments as the
    % actual arguments.
    %
:- pred clone_proc_and_create_call(pred_id::in, pred_info::in, proc_id::in,
    hlds_goal_expr::out, module_info::in, module_info::out) is det.

clone_proc_and_create_call(PredId, PredInfo, ProcId, CallExpr, !ModuleInfo) :-
    pred_info_proc_info(PredInfo, ProcId, ProcInfo),
    proc_info_get_context(ProcInfo, ProcContext),
    proc_info_get_var_table(ProcInfo, ProcVarTable),
    proc_info_get_headvars(ProcInfo, ProcHeadVars),
    proc_info_get_inst_varset(ProcInfo, ProcInstVarSet),
    proc_info_get_argmodes(ProcInfo, ProcHeadModes),
    proc_info_get_inferred_determinism(ProcInfo, ProcDetism),
    proc_info_get_goal(ProcInfo, ProcGoal),
    proc_info_get_rtti_varmaps(ProcInfo, ProcRttiVarMaps),
    proc_info_get_has_parallel_conj(ProcInfo, HasParallelConj),
    proc_info_get_var_name_remap(ProcInfo, VarNameRemap),
    SeqNum = item_no_seq_num,
    proc_info_create(ProcContext, SeqNum, ProcVarTable, ProcHeadVars,
        ProcInstVarSet, ProcHeadModes, detism_decl_none, ProcDetism, ProcGoal,
        ProcRttiVarMaps, address_is_not_taken, HasParallelConj, VarNameRemap,
        NewProcInfo),
    ModuleName = pred_info_module(PredInfo),
    OrigPredName = pred_info_name(PredInfo),
    PredOrFunc = pred_info_is_pred_or_func(PredInfo),
    Transform = tn_io_tabling(PredOrFunc),
    make_transformed_pred_name(OrigPredName, Transform, NewPredName),
    pred_info_get_origin(PredInfo, OrigOrigin),
    Origin = origin_proc_transform(proc_transform_io_tabling, OrigOrigin,
        PredId, ProcId),
    pred_info_get_context(PredInfo, PredContext),
    pred_info_get_arg_types(PredInfo, PredArgTypes),
    pred_info_get_typevarset(PredInfo, PredTypeVarSet),
    pred_info_get_exist_quant_tvars(PredInfo, PredExistQVars),
    pred_info_get_class_context(PredInfo, PredClassContext),
    pred_info_get_assertions(PredInfo, PredAssertions),
    pred_info_get_markers(PredInfo, Markers),
    pred_info_get_goal_type(PredInfo, GoalType),
    pred_info_create(PredOrFunc, ModuleName, NewPredName,
        PredContext, Origin, pred_status(status_local), Markers,
        PredArgTypes, PredTypeVarSet, PredExistQVars, PredClassContext,
        PredAssertions, VarNameRemap, GoalType,
        NewProcInfo, NewProcId, NewPredInfo),
    module_info_get_predicate_table(!.ModuleInfo, PredicateTable0),
    predicate_table_insert(NewPredInfo, NewPredId,
        PredicateTable0, PredicateTable),
    module_info_set_predicate_table(PredicateTable, !ModuleInfo),
    CallExpr = plain_call(NewPredId, NewProcId, ProcHeadVars, not_builtin, no,
        qualified(ModuleName, NewPredName)).

:- pred keep_only_output_arg_types(assoc_list(prog_var, mer_type)::in,
    list(var_mode_pos_method)::in, list(mer_type)::out) is det.

keep_only_output_arg_types([], _, []).
keep_only_output_arg_types([_ | _], [], []).
keep_only_output_arg_types([Var - Type | VarTable], [Out | Outs], OutTypes) :-
    Out = var_mode_pos_method(OutVar, _, _, _),
    ( if Var = OutVar then
        keep_only_output_arg_types(VarTable, Outs, OutTypesTail),
        OutTypes = [Type | OutTypesTail]
    else
        keep_only_output_arg_types(VarTable, [Out | Outs], OutTypes)
    ).

:- pred filter_marker(pred_marker::in) is semidet.

filter_marker(Marker) :-
    keep_marker(Marker) = yes.

:- func keep_marker(pred_marker) = bool.

keep_marker(marker_stub) = no.
keep_marker(marker_builtin_stub) = no.
keep_marker(marker_infer_type) = no.
keep_marker(marker_infer_modes) = no.
keep_marker(marker_no_pred_decl) = no.
keep_marker(marker_no_detism_warning) = no.
keep_marker(marker_user_marked_inline) = no.
keep_marker(marker_heuristic_inline) = no.
keep_marker(marker_user_marked_no_inline) = no.
keep_marker(marker_mmc_marked_no_inline) = yes.
keep_marker(marker_consider_used) = no.
keep_marker(marker_class_method) = no.
keep_marker(marker_class_instance_method) = no.
keep_marker(marker_named_class_instance_method) = no.
keep_marker(marker_is_impure) = yes.
keep_marker(marker_is_semipure) = yes.
keep_marker(marker_promised_pure) = yes.
keep_marker(marker_promised_semipure) = yes.
keep_marker(marker_promised_equivalent_clauses) = yes.
keep_marker(marker_terminates) = yes.
keep_marker(marker_does_not_terminate) = yes.
keep_marker(marker_check_termination) = no.
keep_marker(marker_calls_are_fully_qualified) = yes.
keep_marker(marker_mode_check_clauses) = yes.
keep_marker(marker_mutable_access_pred) = yes.
keep_marker(marker_has_require_scope) = yes.
keep_marker(marker_has_incomplete_switch) = yes.
keep_marker(marker_has_format_call) = yes.
keep_marker(marker_has_rhs_lambda) = yes.
keep_marker(marker_fact_table_semantic_errors) = no.

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

:- pred generate_gen_proc_table_info(table_info::in, pred_id::in, proc_id::in,
    var_table::in, tabled_eval_method::in,
    list(table_step_desc)::in, maybe(list(table_step_desc))::in,
    list(var_mode_method)::in, list(var_mode_method)::in,
    proc_table_struct_info::out) is det.

generate_gen_proc_table_info(TableInfo, PredId, ProcId, VarTable, TabledMethod,
        InputSteps, MaybeOutputSteps, InputVars, OutputVars,
        ProcTableStructInfo) :-
    ModuleInfo = TableInfo ^ table_module_info,
    PredInfo = TableInfo ^ table_cur_pred_info,
    ProcInfo = TableInfo ^ table_cur_proc_info,

    RTTIProcLabel = make_rtti_proc_label(ModuleInfo, PredId, ProcId),
    pred_info_get_typevarset(PredInfo, TVarSet),
    proc_info_get_context(ProcInfo, Context),

    InOutHeadVars = InputVars ++ OutputVars,
    allocate_slot_numbers(InOutHeadVars, 1, NumberedInOutHeadVars),
    ArgInfos = list.map(project_var_pos, NumberedInOutHeadVars),
    proc_info_get_rtti_varmaps(ProcInfo, RttiVarMaps),
    continuation_info.generate_table_arg_type_info(VarTable, RttiVarMaps,
        ArgInfos, TableArgTypeInfo),
    NumInputs = list.length(InputVars),
    NumOutputs = list.length(OutputVars),

    ProcTableStructInfo = proc_table_struct_info(RTTIProcLabel, TVarSet,
        Context, NumInputs, NumOutputs, InputSteps, MaybeOutputSteps,
        TableArgTypeInfo, TabledMethod).

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

    % Generate a goal for doing lookups in call tables for
    % loopcheck and memo predicates.
    %
:- pred generate_plain_call_table_lookup_goal(mer_type::in,
    string::in, string::in, list(var_mode_pos_method)::in,
    pred_id::in, proc_id::in, table_attr_statistics::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    prog_var::out, prog_var::out,
    hlds_goal::out, list(table_step_desc)::out) is det.

generate_plain_call_table_lookup_goal(StatusType, PredName,
        SetupMacroName, NumberedVars, PredId, ProcId, Statistics, Context,
        !VarTable, !TableInfo, TableTipVar, StatusVar, Goal, Steps) :-
    generate_call_table_lookup_goals(NumberedVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo, Steps,
        TableTipVar, TableTipArg, InfoArg, LookupForeignArgs,
        LookupPrefixGoals, LookupCodeStr, CallTableTipAssignStr),
    generate_new_table_var("Status", StatusType, is_not_dummy_type,
        !VarTable, StatusVar),
    ModuleInfo = !.TableInfo ^ table_module_info,

    StatusVarName = status_name,
    StatusArg = foreign_arg(StatusVar,
        yes(foreign_arg_name_mode(StatusVarName, out_mode)),
        StatusType, bp_native_if_possible),
    DebugArgStr = get_debug_arg_string(!.TableInfo),
    BackArgStr = get_back_arg_string(!.TableInfo),
    MainPredCodeStr = "\t" ++ SetupMacroName ++ "(" ++
        DebugArgStr ++ ", " ++ BackArgStr ++ ", " ++
        cur_table_node_name ++ ", " ++ StatusVarName ++ ");\n",
    Args = [InfoArg, TableTipArg, StatusArg],
    BoundVars = [TableTipVar, StatusVar],
    CodeStr =
        "\tMR_TrieNode " ++ cur_table_node_name ++ ";\n" ++
        "\tMR_TrieNode " ++ next_table_node_name ++ ";\n\n" ++
        LookupCodeStr ++
        CallTableTipAssignStr ++
        MainPredCodeStr,
    table_call_foreign_proc(ModuleInfo, PredName,
        Args, LookupForeignArgs, instmap_delta_bind_vars(BoundVars),
        detism_det, purity_impure, tabling_c_attributes_no_dupl,
        CodeStr, Context, SetupGoal0),
    attach_call_table_tip(SetupGoal0, SetupGoal),
    LookupSetupGoals = LookupPrefixGoals ++ [SetupGoal],

    GoalExpr = conj(plain_conj, LookupSetupGoals),
    Vars = list.map(project_var, NumberedVars),
    set_of_var.list_to_set([StatusVar, TableTipVar | Vars], NonLocals),
    goal_info_init_hide(NonLocals,
        instmap_delta_bind_vars([TableTipVar, StatusVar]),
        detism_det, purity_impure, Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

    % Generate a goal for doing lookups in call tables for
    % model_non memo predicates.
    %
:- pred generate_memo_non_call_table_lookup_goal(list(var_mode_pos_method)::in,
    pred_id::in, proc_id::in, table_attr_statistics::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    prog_var::out, prog_var::out,
    hlds_goal::out, list(table_step_desc)::out) is det.

generate_memo_non_call_table_lookup_goal(NumberedVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo,
        RecordVar, StatusVar, Goal, Steps) :-
    generate_call_table_lookup_goals(NumberedVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo, Steps,
        _TableTipVar, _TableTipArg, InfoArg, LookupForeignArgs,
        LookupPrefixGoals, LookupCodeStr, _CallTableTipAssignStr),
    ModuleInfo = !.TableInfo ^ table_module_info,
    generate_new_table_var("Record", memo_non_record_type, is_not_dummy_type,
        !VarTable, RecordVar),
    generate_new_table_var("Status", memo_non_status_type, is_not_dummy_type,
        !VarTable, StatusVar),
    SetupPredName = "table_memo_non_setup",
    SetupMacroName = "MR_tbl_memo_non_setup",
    BoundVars = [RecordVar, StatusVar],
    RecordVarName = memo_non_record_name,
    StatusVarName = status_name,
    RecordArg = foreign_arg(RecordVar,
        yes(foreign_arg_name_mode(RecordVarName, out_mode)),
        memo_non_record_type, bp_native_if_possible),
    StatusArg = foreign_arg(StatusVar,
        yes(foreign_arg_name_mode(StatusVarName, out_mode)),
        memo_non_status_type, bp_native_if_possible),
    Args = [InfoArg, RecordArg, StatusArg],
    LookupDeclCodeStr =
        "\tMR_TrieNode " ++ cur_table_node_name ++ ";\n" ++
        "\tMR_TrieNode " ++ next_table_node_name ++ ";\n\n" ++
        LookupCodeStr,
    DebugArgStr = get_debug_arg_string(!.TableInfo),
    BackArgStr = get_back_arg_string(!.TableInfo),
    PredCodeStr = "\t" ++ SetupMacroName ++ "(" ++
        DebugArgStr ++ ", " ++ BackArgStr ++ ", " ++
        cur_table_node_name ++ ", " ++ RecordVarName ++ ", " ++
        StatusVarName ++ ");\n",
    table_call_foreign_proc(ModuleInfo, SetupPredName,
        Args, LookupForeignArgs,
        instmap_delta_bind_vars(BoundVars),
        detism_det, purity_impure, tabling_c_attributes_no_dupl,
        LookupDeclCodeStr ++ PredCodeStr, Context, SetupGoal0),
    attach_call_table_tip(SetupGoal0, SetupGoal),
    LookupSetupGoals = LookupPrefixGoals ++ [SetupGoal],

    GoalExpr = conj(plain_conj, LookupSetupGoals),
    Vars = list.map(project_var, NumberedVars),
    set_of_var.list_to_set([StatusVar, RecordVar | Vars], NonLocals),
    goal_info_init_hide(NonLocals,
        instmap_delta_bind_vars([RecordVar, StatusVar]),
        detism_det, purity_impure, Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

    % Generate a goal for doing lookups in call tables for
    % minimal model predicates.
    %
:- pred generate_mm_call_table_lookup_goal(list(var_mode_pos_method)::in,
    pred_id::in, proc_id::in, table_attr_statistics::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    prog_var::out, prog_var::out,
    hlds_goal::out, list(table_step_desc)::out) is det.

generate_mm_call_table_lookup_goal(NumberedVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo,
        SubgoalVar, StatusVar, Goal, Steps) :-
    generate_call_table_lookup_goals(NumberedVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo, Steps,
        _TableTipVar, _TableTipArg, InfoArg, LookupForeignArgs,
        LookupPrefixGoals, LookupCodeStr, _CallTableTipAssignStr),
    generate_new_table_var("Subgoal", subgoal_type, is_not_dummy_type,
        !VarTable, SubgoalVar),
    generate_new_table_var("Status", mm_status_type, is_not_dummy_type,
        !VarTable, StatusVar),
    SetupPredName = "table_mm_setup",
    SetupMacroName = "MR_tbl_mm_setup",
    BoundVars = [SubgoalVar, StatusVar],

    SubgoalVarName = subgoal_name,
    StatusVarName = status_name,
    SubgoalArg = foreign_arg(SubgoalVar,
        yes(foreign_arg_name_mode(SubgoalVarName, out_mode)),
        subgoal_type, bp_native_if_possible),
    StatusArg = foreign_arg(StatusVar,
        yes(foreign_arg_name_mode(StatusVarName, out_mode)),
        mm_status_type, bp_native_if_possible),
    Args = [InfoArg, SubgoalArg, StatusArg],
    LookupDeclStr =
        "\tMR_TrieNode " ++ cur_table_node_name ++ ";\n" ++
        "\tMR_TrieNode " ++ next_table_node_name ++ ";\n\n",
    DebugArgStr = get_debug_arg_string(!.TableInfo),
    BackArgStr = get_back_arg_string(!.TableInfo),
    SetupCodeStr = "\t" ++ SetupMacroName ++ "(" ++
        DebugArgStr ++ ", " ++ BackArgStr ++ ", " ++
        cur_table_node_name ++ ", " ++ SubgoalVarName ++ ", " ++
        StatusVarName ++ ");\n",
    CodeStr = LookupDeclStr ++ LookupCodeStr ++ SetupCodeStr,
    ModuleInfo = !.TableInfo ^ table_module_info,
    table_call_foreign_proc(ModuleInfo, SetupPredName,
        Args, LookupForeignArgs,
        instmap_delta_bind_vars(BoundVars),
        detism_det, purity_impure, tabling_c_attributes_no_dupl,
        CodeStr, Context, SetupGoal0),
    attach_call_table_tip(SetupGoal0, SetupGoal),
    LookupSetupGoals = LookupPrefixGoals ++ [SetupGoal],

    GoalExpr = conj(plain_conj, LookupSetupGoals),
    Vars = list.map(project_var, NumberedVars),
    set_of_var.list_to_set([StatusVar, SubgoalVar | Vars], NonLocals),
    goal_info_init_hide(NonLocals,
        instmap_delta_bind_vars([SubgoalVar, StatusVar]),
        detism_det, purity_impure, Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

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

% Utility predicates used when creating table lookup goals.

:- pred generate_call_table_lookup_goals(list(var_mode_pos_method)::in,
    pred_id::in, proc_id::in, table_attr_statistics::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(table_step_desc)::out, prog_var::out,
    foreign_arg::out, foreign_arg::out, list(foreign_arg)::out,
    list(hlds_goal)::out, string::out, string::out) is det.

generate_call_table_lookup_goals(NumberedVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo, InputSteps,
        CallTableTipVar, CallTableTipArg, InfoArg, LookupArgs,
        PrefixGoals, MainCodeStr, CallTableTipVarCodeStr) :-
    InfoToPtrCodeStr = "\t" ++ cur_table_node_name ++ " = " ++
        "&" ++ proc_table_info_name ++ "->MR_pt_tablenode;\n",
    generate_get_table_info_goal(PredId, ProcId, Context,
        proc_table_info_name, InfoArg, GetTableInfoGoal, !VarTable),
    MaybeStatsRef = stats_ref(Statistics, call_table),
    DebugArgStr = get_debug_arg_string(!.TableInfo),
    BackArgStr = get_back_arg_string(!.TableInfo),
    generate_table_lookup_goals(NumberedVars, MaybeStatsRef,
        DebugArgStr, BackArgStr, Context, !VarTable, !TableInfo,
        InputSteps, LookupArgs, LookupPrefixGoals, LookupCodeStr),
    PrefixGoals = [GetTableInfoGoal] ++ LookupPrefixGoals,
    % We ignore _StatsPrefixGoals and _StatsExtraArgs because we always
    % include ProcTableInfoVar in the arguments.
    maybe_record_overall_stats(PredId, ProcId, Context,
        proc_table_info_name, cur_table_node_name, MaybeStatsRef, !VarTable,
        _StatsPrefixGoals, _StatsExtraArgs, StatsCodeStr),
    MainCodeStr = InfoToPtrCodeStr ++ LookupCodeStr ++ StatsCodeStr,
    CallTableTipVarName = "CallTableTipVar",
    generate_new_table_var(CallTableTipVarName, trie_node_type,
        is_not_dummy_type, !VarTable, CallTableTipVar),
    CallTableTipArg = foreign_arg(CallTableTipVar,
        yes(foreign_arg_name_mode(CallTableTipVarName, out_mode)),
        trie_node_type, bp_native_if_possible),
    CallTableTipVarCodeStr =
        "\t" ++ CallTableTipVarName ++ " = " ++ cur_table_node_name ++ ";\n".

:- pred generate_answer_table_lookup_goals(list(var_mode_pos_method)::in,
    pred_id::in, proc_id::in, table_attr_statistics::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(table_step_desc)::out, list(foreign_arg)::out,
    list(hlds_goal)::out, string::out) is det.

generate_answer_table_lookup_goals(NumberedVars, PredId, ProcId, Statistics,
        Context, !VarTable, !TableInfo, OutputSteps, ForeignArgs,
        PrefixGoals, CodeStr) :-
    MaybeStatsRef = stats_ref(Statistics, answer_table),
    DebugArgStr = get_debug_arg_string(!.TableInfo),
    BackArgStr = "MR_FALSE",
    generate_table_lookup_goals(NumberedVars, MaybeStatsRef,
        DebugArgStr, BackArgStr, Context, !VarTable, !TableInfo,
        OutputSteps, LookupArgs, LookupPrefixGoals, LookupCodeStr),
    maybe_record_overall_stats(PredId, ProcId, Context,
        proc_table_info_name, cur_table_node_name,
        MaybeStatsRef, !VarTable,
        StatsPrefixGoals, StatsExtraArgs, StatsCodeStr),
    CodeStr = LookupCodeStr ++ StatsCodeStr,
    ForeignArgs = StatsExtraArgs ++ LookupArgs,
    PrefixGoals = StatsPrefixGoals ++ LookupPrefixGoals.

:- pred maybe_record_overall_stats(pred_id::in, proc_id::in, prog_context::in,
    string::in, string::in, maybe(string)::in,
    var_table::in, var_table::out, list(hlds_goal)::out,
    list(foreign_arg)::out, string::out) is det.

maybe_record_overall_stats(PredId, ProcId, Context, InfoVarName, TipVarName,
        MaybeStatsRef, !VarTable, PrefixGoals, Args, StatsCodeStr) :-
    (
        MaybeStatsRef = no,
        PrefixGoals = [],
        Args = [],
        StatsCodeStr = ""
    ;
        MaybeStatsRef = yes(StatsRef),
        generate_get_table_info_goal(PredId, ProcId, Context, InfoVarName,
            Arg, Goal, !VarTable),
        PrefixGoals = [Goal],
        Args = [Arg],
        StatsCodeStr =
            "\t" ++ StatsRef ++ ".MR_ts_num_lookups++;\n" ++
            "\t" ++ "if (MR_trie_node_seen_before(" ++ TipVarName ++ ")) " ++
                "{\n" ++
            "\t\t" ++ StatsRef ++ ".MR_ts_num_lookups_is_dupl++;\n" ++
            "\t" ++ "}\n"
    ).

:- pred generate_get_table_info_goal(pred_id::in, proc_id::in,
    prog_context::in, string::in, foreign_arg::out, hlds_goal::out,
    var_table::in, var_table::out) is det.

generate_get_table_info_goal(PredId, ProcId, Context, InfoVarName,
        Arg, Goal, !VarTable) :-
    generate_new_table_var("ProcTableInfo", proc_table_info_type,
        is_not_dummy_type, !VarTable, ProcTableInfoVar),
    Arg = foreign_arg(ProcTableInfoVar,
        yes(foreign_arg_name_mode(InfoVarName, in_mode)),
        proc_table_info_type, bp_native_if_possible),
    ShroudedPredProcId = shroud_pred_proc_id(proc(PredId, ProcId)),
    InfoConsId = tabling_info_const(ShroudedPredProcId),
    make_const_construction(Context, ProcTableInfoVar, InfoConsId,
        hlds_goal(GoalExpr, GoalInfo0)),
    goal_info_set_purity(purity_impure, GoalInfo0, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

:- pred attach_call_table_tip(hlds_goal::in, hlds_goal::out) is det.

attach_call_table_tip(Goal0, Goal) :-
    Goal0 = hlds_goal(GoalExpr, GoalInfo0),
    Features0 = goal_info_get_features(GoalInfo0),
    set.insert(feature_call_table_gen, Features0, Features),
    goal_info_set_features(Features, GoalInfo0, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

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

    % Generate a sequence of lookup goals for the given variables.
    % The generated code is used for lookups in both call tables
    % and answer tables.
    %
:- pred generate_table_lookup_goals(list(var_mode_pos_method)::in,
    maybe(string)::in, string::in, string::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(table_step_desc)::out, list(foreign_arg)::out,
    list(hlds_goal)::out, string::out) is det.

generate_table_lookup_goals([], _, _, _, _, !VarTable, !TableInfo,
        [], [], [], "").
generate_table_lookup_goals([VarModePos | NumberedVars], MaybeStatsRef,
        DebugArgStr, BackArgStr, Context, !VarTable, !TableInfo,
        [StepDesc | StepDescs], ForeignArgs ++ RestForeignArgs,
        PrefixGoals ++ RestPrefixGoals, CodeStr ++ RestCodeStr) :-
    VarModePos = var_mode_pos_method(Var, _, VarSeqNum, ArgMethod),
    ModuleInfo = !.TableInfo ^ table_module_info,
    lookup_var_entry(!.VarTable, Var, VarEntry),
    VarName = var_entry_name(Var, VarEntry),
    VarType = VarEntry ^ vte_type,
    CtorCat = classify_type(ModuleInfo, VarType),
    (
        ArgMethod = arg_promise_implied,
        Step = table_trie_step_promise_implied,
        ForeignArgs = [],
        PrefixGoals = [],
        CodeStr = "\t/* promise_implied for " ++ arg_name(VarSeqNum) ++ " */\n"
    ;
        ( ArgMethod = arg_value
        ; ArgMethod = arg_addr
        ),
        gen_lookup_call_for_type(ArgMethod, CtorCat, VarType, Var,
            VarSeqNum, MaybeStatsRef, DebugArgStr, BackArgStr, Context,
            !VarTable, !TableInfo, Step, ForeignArgs, PrefixGoals, CodeStr)
    ),
    StepDesc = table_step_desc(VarName, Step),
    generate_table_lookup_goals(NumberedVars, MaybeStatsRef,
        DebugArgStr, BackArgStr, Context, !VarTable, !TableInfo,
        StepDescs, RestForeignArgs, RestPrefixGoals, RestCodeStr).

:- pred gen_lookup_call_for_type(arg_tabling_method::in,
    type_ctor_category::in, mer_type::in, prog_var::in, int::in,
    maybe(string)::in, string::in, string::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    table_trie_step::out, list(foreign_arg)::out, list(hlds_goal)::out,
    string::out) is det.

gen_lookup_call_for_type(ArgTablingMethod0, CtorCat, Type, ArgVar, VarSeqNum,
        MaybeStatsRef, DebugArgStr, BackArgStr, Context, !VarTable,
        !TableInfo, Step, ExtraArgs, PrefixGoals, CodeStr) :-
    ModuleInfo = !.TableInfo ^ table_module_info,
    ArgName = arg_name(VarSeqNum),
    ForeignArg = foreign_arg(ArgVar,
        yes(foreign_arg_name_mode(ArgName, in_mode)),
        Type, bp_native_if_possible),
    (
        ( CtorCat = ctor_cat_enum(_)
        ; CtorCat = ctor_cat_builtin(cat_builtin_int(_))
        ; CtorCat = ctor_cat_builtin(cat_builtin_char)
        ; CtorCat = ctor_cat_void
        ),
        % Values in these type categories don't have an address.
        ( if ArgTablingMethod0 = arg_addr then
            ArgTablingMethod = arg_value
        else
            ArgTablingMethod = ArgTablingMethod0
        )
    ;
        ( CtorCat = ctor_cat_builtin(cat_builtin_string)
        ; CtorCat = ctor_cat_builtin(cat_builtin_float)
        ; CtorCat = ctor_cat_system(_)
        ; CtorCat = ctor_cat_higher_order
        ; CtorCat = ctor_cat_tuple
        ; CtorCat = ctor_cat_variable
        ; CtorCat = ctor_cat_user(_)
        ; CtorCat = ctor_cat_builtin_dummy
        ),
        ArgTablingMethod = ArgTablingMethod0
    ),
    MaybeStepStatsArgStr = maybe_step_stats_arg_addr(MaybeStatsRef, VarSeqNum),
    (
        ArgTablingMethod = arg_value,
        (
            CtorCat = ctor_cat_enum(cat_enum_mercury),
            type_to_ctor_det(Type, TypeCtor),
            module_info_get_type_table(ModuleInfo, TypeTable),
            get_enum_max_int_tag(TypeTable, TypeCtor, MaxIntTag),
            EnumRange = MaxIntTag + 1,
            LookupMacroName = "MR_tbl_lookup_insert_enum",
            Step = table_trie_step_enum(EnumRange),
            PrefixGoals = [],
            ExtraArgs = [ForeignArg],
            LookupCodeStr = "\t" ++ LookupMacroName ++ "(" ++
                MaybeStepStatsArgStr ++ ", " ++ DebugArgStr ++ ", " ++
                BackArgStr ++ ", " ++ cur_table_node_name ++ ", " ++
                int_to_string(EnumRange) ++ ", " ++ ArgName ++ ", " ++
                next_table_node_name ++ ");\n"
        ;
            % Mercury doesn't know the specific values of the foreign
            % enums, so we cannot use an array as a trie (since we don't
            % know how big the array would have to be). However, hashing
            % the foreign enum will work.
            %
            % XXX The code of this case is the same as the code of the case
            % shared by the builtin types below. The only reason why it is
            % here is that switch detection cannot yet look three levels deep.

            CtorCat = ctor_cat_enum(cat_enum_foreign),
            CatString = "foreign_enum",
            Step = table_trie_step_foreign_enum,
            LookupMacroName = "MR_tbl_lookup_insert_" ++ CatString,
            PrefixGoals = [],
            ExtraArgs = [ForeignArg],
            LookupCodeStr = "\t" ++ LookupMacroName ++ "(" ++
                MaybeStepStatsArgStr ++ ", " ++ DebugArgStr ++ ", " ++
                BackArgStr ++ ", " ++ cur_table_node_name ++ ", " ++
                ArgName ++ ", " ++ next_table_node_name ++ ");\n"
        ;
            % XXX The code of this case is the same as the code of the case
            % shared by the builtin types below. The only reason why it is
            % here is that switch detection cannot yet look three levels deep.

            ( CtorCat = ctor_cat_system(cat_system_type_info)
            ; CtorCat = ctor_cat_system(cat_system_type_ctor_info)
            ),
            CatString = "typeinfo",
            Step = table_trie_step_typeinfo,
            LookupMacroName = "MR_tbl_lookup_insert_" ++ CatString,
            PrefixGoals = [],
            ExtraArgs = [ForeignArg],
            LookupCodeStr = "\t" ++ LookupMacroName ++ "(" ++
                MaybeStepStatsArgStr ++ ", " ++ DebugArgStr ++ ", " ++
                BackArgStr ++ ", " ++ cur_table_node_name ++ ", " ++
                ArgName ++ ", " ++ next_table_node_name ++ ");\n"
        ;
            (
                CtorCat = ctor_cat_builtin(cat_builtin_int(IntType)),
                int_type_module_name(IntType, CatString),
                Step = table_trie_step_int(IntType)
            ;
                CtorCat = ctor_cat_builtin(cat_builtin_char),
                CatString = "char",
                Step = table_trie_step_char
            ;
                CtorCat = ctor_cat_builtin(cat_builtin_string),
                CatString = "string",
                Step = table_trie_step_string
            ;
                CtorCat = ctor_cat_builtin(cat_builtin_float),
                CatString = "float",
                Step = table_trie_step_float
            ),
            LookupMacroName = "MR_tbl_lookup_insert_" ++ CatString,
            PrefixGoals = [],
            ExtraArgs = [ForeignArg],
            LookupCodeStr = "\t" ++ LookupMacroName ++ "(" ++
                MaybeStepStatsArgStr ++ ", " ++ DebugArgStr ++ ", " ++
                BackArgStr ++ ", " ++ cur_table_node_name ++ ", " ++
                ArgName ++ ", " ++ next_table_node_name ++ ");\n"
        ;
            ( CtorCat = ctor_cat_higher_order
            ; CtorCat = ctor_cat_tuple
            ; CtorCat = ctor_cat_variable
            ; CtorCat = ctor_cat_user(_)
            ),
            MaybeAddrString = "",
            IsAddr = table_value,
            gen_general_lookup_call(IsAddr, MaybeAddrString, Type, ForeignArg,
                ArgName, VarSeqNum, MaybeStatsRef, DebugArgStr, BackArgStr,
                Context, !VarTable, !TableInfo, Step, ExtraArgs,
                PrefixGoals, LookupCodeStr)
        ;
            CtorCat = ctor_cat_builtin_dummy,
            Step = table_trie_step_dummy,
            PrefixGoals = [],
            ExtraArgs = [],
            LookupCodeStr = "\t" ++ next_table_node_name ++ " = " ++
                cur_table_node_name ++ ";\n"
        ;
            CtorCat = ctor_cat_void,
            unexpected($pred, "void")
        ;
            CtorCat = ctor_cat_system(cat_system_typeclass_info),
            unexpected($pred, "typeclass_info_type")
        ;
            CtorCat = ctor_cat_system(cat_system_base_typeclass_info),
            unexpected($pred, "base_typeclass_info_type")
        )
    ;
        ArgTablingMethod = arg_addr,
        (
            CtorCat = ctor_cat_enum(_),
            unexpected($pred, "tabling enums by addr")
        ;
            CtorCat = ctor_cat_builtin(cat_builtin_int(_)),
            unexpected($pred, "tabling integer type by addr")
        ;
            CtorCat = ctor_cat_builtin(cat_builtin_char),
            unexpected($pred, "tabling chars by addr")
        ;
            ( CtorCat = ctor_cat_builtin(cat_builtin_string)
            ; CtorCat = ctor_cat_builtin(cat_builtin_float)
            ; CtorCat = ctor_cat_system(_)
            ; CtorCat = ctor_cat_higher_order
            ; CtorCat = ctor_cat_tuple
            ; CtorCat = ctor_cat_variable
            ; CtorCat = ctor_cat_user(_)
            ),
            MaybeAddrString = "_addr",
            IsAddr = table_addr,
            gen_general_lookup_call(IsAddr, MaybeAddrString, Type, ForeignArg,
                ArgName, VarSeqNum, MaybeStatsRef, DebugArgStr, BackArgStr,
                Context, !VarTable, !TableInfo, Step, ExtraArgs,
                PrefixGoals, LookupCodeStr)
        ;
            CtorCat = ctor_cat_builtin_dummy,
            unexpected($pred, "tabling dummies by addr")
        ;
            CtorCat = ctor_cat_void,
            unexpected($pred, "void")
        )
    ;
        ArgTablingMethod = arg_promise_implied,
        unexpected($pred, "arg_promise_implied")
    ),
    UpdateCurNodeCodeStr = "\t" ++ cur_table_node_name ++ " = " ++
        next_table_node_name ++ ";\n",
    (
        MaybeStatsRef = no,
        CodeStr = LookupCodeStr ++ UpdateCurNodeCodeStr
    ;
        MaybeStatsRef = yes(StatsRef),
        StepStatsArgStr = step_stats_arg_addr(StatsRef, VarSeqNum),
        NextVarName = next_table_node_name,
        LookupStatsCodeStr =
            "\t" ++ StepStatsArgStr ++ ".MR_tss_num_lookups++;\n" ++
            "\t" ++ "if (MR_trie_node_seen_before(" ++ NextVarName ++ "))" ++
                "{\n" ++
            "\t\t" ++ StepStatsArgStr ++ ".MR_tss_num_lookups_is_dupl++;\n" ++
            "\t" ++ "}\n",
        CodeStr = LookupCodeStr ++ LookupStatsCodeStr ++ UpdateCurNodeCodeStr
    ).

:- pred gen_general_lookup_call(table_value_or_addr::in, string::in,
    mer_type::in, foreign_arg::in, string::in, int::in, maybe(string)::in,
    string::in, string::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    table_trie_step::out, list(foreign_arg)::out, list(hlds_goal)::out,
    string::out) is det.

gen_general_lookup_call(IsAddr, MaybeAddrString, Type, ForeignArg, ArgName,
        VarSeqNum, MaybeStatsRef, DebugArgStr, BackArgStr, Context,
        !VarTable, !TableInfo, Step, ExtraArgs, PrefixGoals, LookupCodeStr) :-
    type_vars_in_type(Type, TypeVars),
    (
        TypeVars = [],
        MaybePolyString = "",
        IsPoly = table_is_mono
    ;
        TypeVars = [_ | _],
        MaybePolyString = "_poly",
        IsPoly = table_is_poly
    ),
    Step = table_trie_step_general(Type, IsPoly, IsAddr),
    LookupMacroName = "MR_tbl_lookup_insert_gen" ++
        MaybePolyString ++ MaybeAddrString,
    table_gen_make_type_info_var(Type, Context, !VarTable,
        !TableInfo, TypeInfoVar, PrefixGoals),
    TypeInfoArgName = "input_typeinfo" ++ int_to_string(VarSeqNum),
    lookup_var_type(!.VarTable, TypeInfoVar, TypeInfoType),
    ForeignTypeInfoArg = foreign_arg(TypeInfoVar,
        yes(foreign_arg_name_mode(TypeInfoArgName, in_mode)),
        TypeInfoType, bp_native_if_possible),
    ExtraArgs = [ForeignTypeInfoArg, ForeignArg],
    StepStatsArgStr = maybe_step_stats_arg_addr(MaybeStatsRef, VarSeqNum),
    LookupCodeStr = "\t" ++ LookupMacroName ++ "(" ++
        StepStatsArgStr ++ ", " ++ DebugArgStr ++ ", " ++ BackArgStr ++ ", " ++
        cur_table_node_name ++ ", " ++ TypeInfoArgName ++ ", " ++
        ArgName ++ ", " ++ next_table_node_name ++ ");\n".

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

    % Generate a goal for saving the output arguments in an answer block
    % in memo predicates.
    %
:- pred generate_memo_save_goal(list(var_mode_pos_method(T))::in,
    prog_var::in, int::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(hlds_goal)::out) is det.

generate_memo_save_goal(NumberedSaveVars, TableTipVar, BlockSize,
        Context, !VarTable, !TableInfo, Goals) :-
    ModuleInfo = !.TableInfo ^ table_module_info,
    TipVarName = cur_table_node_name,
    TableArg = foreign_arg(TableTipVar,
        yes(foreign_arg_name_mode(TipVarName, in_mode)),
        trie_node_type, bp_native_if_possible),
    ( if BlockSize > 0 then
        CreatePredName = "table_memo_fill_answer_block_shortcut",
        CreateMacroName = "MR_tbl_memo_create_answer_block",
        generate_all_save_goals(NumberedSaveVars, TipVarName,
            BlockSize, CreateMacroName, Context, !VarTable,
            !TableInfo, SaveArgs, SavePrefixGoals, SaveDeclCode, SaveCode),
        table_call_foreign_proc(ModuleInfo, CreatePredName,
            [TableArg], SaveArgs, instmap_delta_bind_no_var,
            detism_det, purity_impure, tabling_c_attributes_dupl,
            SaveDeclCode ++ SaveCode, Context, SaveGoal),
        Goals = SavePrefixGoals ++ [SaveGoal]
    else
        DebugArgStr = get_debug_arg_string(!.TableInfo),
        MarkAsSucceededPredName = "table_memo_mark_as_succeeded",
        MarkAsSucceededMacroName = "MR_tbl_memo_mark_as_succeeded",
        MarkAsSucceededCode = MarkAsSucceededMacroName ++
            "(" ++ DebugArgStr ++ ", " ++ cur_table_node_name ++ ");",
        table_call_foreign_proc(ModuleInfo, MarkAsSucceededPredName,
            [TableArg], [], instmap_delta_bind_no_var,
            detism_det, purity_impure, tabling_c_attributes_dupl,
            MarkAsSucceededCode, Context, SaveGoal),
        Goals = [SaveGoal]
    ).

    % Generate a goal for saving the output arguments in an answer block
    % in model_non memo predicates.
    %
:- pred generate_memo_non_save_goals(list(var_mode_pos_method)::in,
    pred_id::in, proc_id::in, prog_var::in, int::in,
    table_attr_statistics::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(table_step_desc)::out, list(hlds_goal)::out) is det.

generate_memo_non_save_goals(NumberedSaveVars, PredId, ProcId,
        RecordVar, BlockSize, Statistics, Context, !VarTable,
        !TableInfo, OutputSteps, Goals) :-
    ModuleInfo = !.TableInfo ^ table_module_info,
    RecordName = memo_non_record_name,
    RecordArg = foreign_arg(RecordVar,
        yes(foreign_arg_name_mode(RecordName, in_mode)),
        memo_non_record_type, bp_native_if_possible),

    generate_answer_table_lookup_goals(NumberedSaveVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo, OutputSteps,
        LookupForeignArgs, LookupPrefixGoals, LookupCodeStr),

    CreateAnswerBlockMacroName = "MR_tbl_memo_non_create_answer_block",
    generate_all_save_goals(NumberedSaveVars, memo_non_record_name, BlockSize,
        CreateAnswerBlockMacroName, Context, !VarTable, !TableInfo,
        _SaveForeignArgs, _SavePrefixGoals, SaveDeclCodeStr, CreateSaveCode),

    GetMacroName = "MR_tbl_memo_non_get_answer_table",
    DuplCheckPredName = "table_memo_non_answer_is_not_duplicate_shortcut",
    DuplCheckMacroName = "MR_tbl_memo_non_answer_is_not_duplicate",

    DebugArgStr = get_debug_arg_string(!.TableInfo),
    SuccName = "succeeded",
    LookupDeclCodeStr =
        "\tMR_TrieNode " ++ cur_table_node_name ++ ";\n" ++
        "\tMR_TrieNode " ++ next_table_node_name ++ ";\n" ++
        "\tMR_bool " ++ SuccName ++ ";\n",
    GetCodeStr =
        "\t" ++ GetMacroName ++ "(" ++ DebugArgStr ++ ", " ++
            RecordName ++ ", " ++ cur_table_node_name ++ ");\n" ++
        LookupCodeStr,
    DuplCheckCodeStr =
        "\t" ++ DuplCheckMacroName ++ "(" ++ DebugArgStr ++ ", " ++
            cur_table_node_name ++ ", " ++ SuccName ++ ");\n",
    AssignSuccessCodeStr =
        "\t" ++ success_indicator_name ++ " = " ++ SuccName ++ ";\n",
    CodeStr = LookupDeclCodeStr ++ SaveDeclCodeStr ++ "\n" ++
        GetCodeStr ++ LookupCodeStr ++
        DuplCheckCodeStr ++
        "\tif (" ++ SuccName ++ ") {\n" ++ CreateSaveCode ++ "\t}\n" ++
        AssignSuccessCodeStr,
    table_call_foreign_proc(ModuleInfo, DuplCheckPredName,
        [RecordArg], LookupForeignArgs, instmap_delta_bind_no_var,
        detism_semi, purity_impure, tabling_c_attributes_dupl,
        CodeStr, Context, DuplicateCheckSaveGoal),
    Goals = LookupPrefixGoals ++ [DuplicateCheckSaveGoal].

    % Generate a goal for saving the output arguments in an answer block
    % in minimal model predicates.
    %
:- pred generate_mm_save_goals(list(var_mode_pos_method)::in,
    prog_var::in, pred_id::in, proc_id::in, int::in,
    table_attr_statistics::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(table_step_desc)::out, list(hlds_goal)::out) is det.

generate_mm_save_goals(NumberedSaveVars, SubgoalVar, PredId, ProcId, BlockSize,
        Statistics, Context, !VarTable, !TableInfo, OutputSteps, Goals) :-
    ModuleInfo = !.TableInfo ^ table_module_info,
    DebugArgStr = get_debug_arg_string(!.TableInfo),

    generate_answer_table_lookup_goals(NumberedSaveVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo, OutputSteps,
        LookupForeignArgs, LookupPrefixGoals, LookupCodeStr),

    GetMacroName = "MR_tbl_mm_get_answer_table",
    CreateMacroName = "MR_tbl_mm_create_answer_block",
    DuplCheckPredName = "table_mm_answer_is_not_duplicate_shortcut",
    DuplCheckMacroName = "MR_tbl_mm_answer_is_not_duplicate",

    generate_all_save_goals(NumberedSaveVars, subgoal_name, BlockSize,
        CreateMacroName, Context, !VarTable, !TableInfo,
        _SaveArgs, _SavePrefixGoals, SaveDeclCode, CreateSaveCode),

    SubgoalName = subgoal_name,
    Args = [foreign_arg(SubgoalVar,
        yes(foreign_arg_name_mode(SubgoalName, in_mode)),
        subgoal_type, bp_native_if_possible)],
    SuccName = "succeeded",
    LookupDeclCodeStr =
        "\tMR_TrieNode " ++ cur_table_node_name ++ ";\n" ++
        "\tMR_TrieNode " ++ next_table_node_name ++ ";\n" ++
        "\tMR_bool " ++ SuccName ++ ";\n",
    GetCodeStr =
        "\t" ++ GetMacroName ++ "(" ++ DebugArgStr ++ ", " ++
            SubgoalName ++ ", " ++ cur_table_node_name ++ ");\n",
    DuplCheckCodeStr =
        "\t" ++ DuplCheckMacroName ++ "(" ++ DebugArgStr ++ ", " ++
            cur_table_node_name ++ ", " ++ SuccName ++ ");\n",
    CondSaveStr = "\tif (" ++ SuccName ++ ") {\n" ++
        CreateSaveCode ++ "\t}\n",
    AssignSuccessCodeStr =
        "\t" ++ success_indicator_name ++ " = " ++ SuccName ++ ";\n",
    CodeStr = LookupDeclCodeStr ++ SaveDeclCode ++
        GetCodeStr ++ LookupCodeStr ++ DuplCheckCodeStr ++
        CondSaveStr ++ AssignSuccessCodeStr,
    table_call_foreign_proc(ModuleInfo, DuplCheckPredName,
        Args, LookupForeignArgs, instmap_delta_bind_no_var,
        detism_semi, purity_impure, tabling_c_attributes_dupl,
        CodeStr, Context, DuplicateCheckSaveGoal),
    Goals = LookupPrefixGoals ++ [DuplicateCheckSaveGoal].

    % Generate a save goal for the given variables.
    %
:- pred generate_all_save_goals(list(var_mode_pos_method(T))::in,
    string::in, int::in, string::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(foreign_arg)::out, list(hlds_goal)::out,
    string::out, string::out) is det.

generate_all_save_goals(NumberedSaveVars, BaseVarName, BlockSize,
        CreateMacroName, Context, !VarTable, !TableInfo,
        SaveArgs, SavePrefixGoals, SaveDeclCodeStr, CreateSaveCodeStr) :-
    DebugArgStr = get_debug_arg_string(!.TableInfo),
    generate_save_goals(NumberedSaveVars, DebugArgStr, Context,
        !VarTable, !TableInfo, SaveArgs, SavePrefixGoals, SaveCodeStr),
    SaveDeclCodeStr = "\tMR_AnswerBlock " ++ answer_block_name ++ ";\n",
    CreateCodeStr = "\t" ++ CreateMacroName ++ "(" ++ DebugArgStr ++ ", " ++
        BaseVarName ++ ", " ++ int_to_string(BlockSize) ++ ", " ++
        answer_block_name ++ ");\n",
    CreateSaveCodeStr = CreateCodeStr ++ SaveCodeStr.

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

    % Generate a sequence of save goals for the given variables.
    %
:- pred generate_own_stack_save_return_goal(list(var_mode_pos_method)::in,
    prog_var::in, pred_id::in, proc_id::in, int::in,
    table_attr_statistics::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(table_step_desc)::out, list(hlds_goal)::out) is det.

generate_own_stack_save_return_goal(NumberedOutputVars, GeneratorVar,
        PredId, ProcId, BlockSize, Statistics, Context, !VarTable,
        !TableInfo, OutputSteps, Goals) :-
    GeneratorName = generator_name,
    GeneratorArg = foreign_arg(GeneratorVar,
        yes(foreign_arg_name_mode(GeneratorName, in_mode)),
        generator_type, bp_native_if_possible),
    DebugArgStr = get_debug_arg_string(!.TableInfo),

    generate_answer_table_lookup_goals(NumberedOutputVars, PredId, ProcId,
        Statistics, Context, !VarTable, !TableInfo, OutputSteps,
        LookupForeignArgs, LookupPrefixGoals, LookupCodeStr),

    generate_save_goals(NumberedOutputVars, DebugArgStr, Context,
        !VarTable, !TableInfo, _SaveArgs, SavePrefixGoals, SaveCodeStr),

    GetMacroName = "MR_table_mmos_get_answer_table",
    CreateMacroName = "MR_tbl_mmos_create_answer_block",
    DuplCheckPredName = "table_mmos_answer_is_not_duplicate_shortcut",
    DuplCheckMacroName = "MR_tbl_mmos_answer_is_not_duplicate",

    Args = [GeneratorArg],
    SuccName = "succeeded",
    LookupSaveDeclCodeStr =
        "\tMR_TrieNode " ++ cur_table_node_name ++ ";\n" ++
        "\tMR_TrieNode " ++ next_table_node_name ++ ";\n" ++
        "\tMR_AnswerBlock " ++ answer_block_name ++ ";\n" ++
        "\tMR_bool " ++ SuccName ++ ";\n\n",
    GetCodeStr = "\t" ++ cur_table_node_name ++ " = " ++
        GetMacroName ++ "(" ++ GeneratorName ++ ");\n",
    DuplCheckCodeStr =
        "\t" ++ DuplCheckMacroName ++ "(" ++ DebugArgStr ++ ", " ++
            cur_table_node_name ++ ", " ++ SuccName ++ ");\n",
    AssignSuccessCodeStr =
        "\t" ++ success_indicator_name ++ " = " ++ SuccName ++ ";\n",
    CreateCodeStr = "\t" ++ CreateMacroName ++ "(" ++ DebugArgStr ++ ", " ++
        GeneratorName ++ ", " ++ int_to_string(BlockSize) ++ ", " ++
        answer_block_name ++ ");\n",
    SetupReturnCodeStr = "\t" ++ returning_generator_locn ++ " = " ++
        GeneratorName ++ ";\n",
    CreateSaveSetupReturnCodeStr = CreateCodeStr ++ SaveCodeStr ++
        SetupReturnCodeStr,
    CondSaveCodeStr = "\tif (" ++ SuccName ++ ") {\n" ++
        CreateSaveSetupReturnCodeStr ++ "\t}\n",
    CodeStr = LookupSaveDeclCodeStr ++ GetCodeStr ++ LookupCodeStr ++
        DuplCheckCodeStr ++ CondSaveCodeStr ++ AssignSuccessCodeStr,
    ModuleInfo = !.TableInfo ^ table_module_info,
    table_call_foreign_proc(ModuleInfo, DuplCheckPredName,
        Args, LookupForeignArgs, instmap_delta_bind_no_var,
        detism_semi, purity_impure, tabling_c_attributes_dupl,
        CodeStr, Context, DuplicateCheckSaveGoal),
    Goals = LookupPrefixGoals ++ SavePrefixGoals ++
        [DuplicateCheckSaveGoal].

:- pred generate_save_goals(list(var_mode_pos_method(T))::in, string::in,
    term.context::in, var_table::in, var_table::out,
    table_info::in, table_info::out,
    list(foreign_arg)::out, list(hlds_goal)::out, string::out) is det.

generate_save_goals([], _, _, !VarTable, !TableInfo, [], [], "").
generate_save_goals([NumberedVar | NumberedRest], DebugArgStr, Context,
        !VarTable, !TableInfo, Args ++ RestArgs,
        PrefixGoals ++ RestPrefixGoals, CodeStr ++ RestCodeStr) :-
    NumberedVar = var_mode_pos_method(Var, _Mode, Offset, _),
    ModuleInfo = !.TableInfo ^ table_module_info,
    lookup_var_type(!.VarTable, Var, VarType),
    CtorCat = classify_type(ModuleInfo, VarType),
    gen_save_call_for_type(CtorCat, VarType, Var, Offset, DebugArgStr, Context,
        !VarTable, !TableInfo, Args, PrefixGoals, CodeStr),
    generate_save_goals(NumberedRest, DebugArgStr, Context, !VarTable,
        !TableInfo, RestArgs, RestPrefixGoals, RestCodeStr).

:- pred gen_save_call_for_type(type_ctor_category::in, mer_type::in,
    prog_var::in, int::in, string::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(foreign_arg)::out, list(hlds_goal)::out, string::out) is det.

gen_save_call_for_type(CtorCat, Type, Var, Offset, DebugArgStr, Context,
        !VarTable, !TableInfo, Args, PrefixGoals, CodeStr) :-
    Name = arg_name(Offset),
    ForeignArg = foreign_arg(Var, yes(foreign_arg_name_mode(Name, in_mode)),
        Type, bp_native_if_possible),
    ( if type_is_io_state(Type) then
        SaveMacroName = "MR_tbl_save_io_state_answer",
        Args = [ForeignArg],
        PrefixGoals = [],
        CodeStr = "\t" ++ SaveMacroName ++ "(" ++ DebugArgStr ++ ", " ++
            answer_block_name ++ ", " ++ int_to_string(Offset) ++ ", "
            ++ Name ++ ");\n"
    else if builtin_type(CtorCat) = no then
        % If we used ForeignArg instead of GenericForeignArg, then
        % Var would be unboxed when assigned to Name, which we don't want.
        GenericForeignArg = foreign_arg(Var,
            yes(foreign_arg_name_mode(Name, in_mode)),
            dummy_type_var, bp_native_if_possible),
        table_gen_make_type_info_var(Type, Context, !VarTable,
            !TableInfo, TypeInfoVar, PrefixGoals),
        TypeInfoName = "save_arg_typeinfo" ++ int_to_string(Offset),
        lookup_var_type(!.VarTable, TypeInfoVar, TypeInfoType),
        TypeInfoForeignArg = foreign_arg(TypeInfoVar,
            yes(foreign_arg_name_mode(TypeInfoName, in_mode)),
            TypeInfoType, bp_native_if_possible),
        SaveMacroName = "MR_tbl_save_any_answer",
        Args = [GenericForeignArg, TypeInfoForeignArg],
        CodeStr = "\t" ++ SaveMacroName ++ "(" ++ DebugArgStr ++ ", " ++
            answer_block_name ++ ", " ++ int_to_string(Offset) ++ ", " ++
            TypeInfoName ++ ", " ++ Name ++ ");\n"
    else
        type_save_category(CtorCat, CatString),
        SaveMacroName = "MR_tbl_save_" ++ CatString ++ "_answer",
        Args = [ForeignArg],
        PrefixGoals = [],
        CodeStr = "\t" ++ SaveMacroName ++ "(" ++ DebugArgStr ++ ", " ++
            answer_block_name ++ ", " ++ int_to_string(Offset) ++ ", "
            ++ Name ++ ");\n"
    ).

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

    % Generate a goal for restoring the output arguments from
    % an answer block in memo predicates.
    %
:- pred generate_memo_restore_goal(list(var_mode_pos_method(T))::in,
    instmap_delta::in, prog_var::in, term.context::in,
    var_table::in, var_table::out, table_info::in, hlds_goal::out) is det.

generate_memo_restore_goal(NumberedOutputVars, OrigInstMapDelta, TipVar,
        Context, !VarTable, TableInfo, Goal) :-
    (
        NumberedOutputVars = [_ | _],
        DebugArgStr = get_debug_arg_string(TableInfo),
        ModuleInfo = TableInfo ^ table_module_info,
        generate_restore_goals(ModuleInfo, OrigInstMapDelta, DebugArgStr,
            NumberedOutputVars, RestoreInstMapDeltaSrc, RestoreArgs,
            RestoreCodeStr, !VarTable),
        BaseVarName = base_name,
        Arg = foreign_arg(TipVar,
            yes(foreign_arg_name_mode(BaseVarName, in_mode)),
            trie_node_type, bp_native_if_possible),
        Args = [Arg],
        GetPredName = "table_memo_get_answer_block_shortcut",
        GetMacroName = "MR_tbl_memo_get_answer_block",
        DeclCodeStr = "\tMR_AnswerBlock " ++ answer_block_name ++ ";\n",
        GetRestoreCodeStr = "\t" ++ GetMacroName ++ "(" ++
            DebugArgStr ++ ", " ++ BaseVarName ++ ", " ++
            answer_block_name ++ ");\n" ++
            RestoreCodeStr,
        table_call_foreign_proc(ModuleInfo, GetPredName,
            Args, RestoreArgs,
            instmap_delta_from_assoc_list(RestoreInstMapDeltaSrc),
            detism_det, purity_semipure, tabling_c_attributes_dupl,
            DeclCodeStr ++ GetRestoreCodeStr, Context, ShortcutGoal),
        Goal = ShortcutGoal
    ;
        NumberedOutputVars = [],
        Goal = true_goal
    ).

    % Generate a goal for restoring the output arguments from
    % an answer block in model_non memo predicates.
    %
:- pred generate_memo_non_restore_goal(determinism::in,
    list(var_mode_pos_method)::in, instmap_delta::in, prog_var::in,
    term.context::in, var_table::in, var_table::out, table_info::in,
    hlds_goal::out) is det.

generate_memo_non_restore_goal(Detism, NumberedOutputVars, OrigInstMapDelta,
        RecordVar, Context, !VarTable, TableInfo, Goal) :-
    ( if Detism = detism_multi then
        ReturnAllAns = "table_memo_return_all_answers_multi"
    else if Detism = detism_non then
        ReturnAllAns = "table_memo_return_all_answers_nondet"
    else
        unexpected($pred, "invalid determinism")
    ),
    generate_new_table_var("AnswerBlock", answer_block_type, is_not_dummy_type,
        !VarTable, AnswerBlockVar),
    ModuleInfo = TableInfo ^ table_module_info,
    table_plain_call(ModuleInfo, ReturnAllAns,
        [RecordVar, AnswerBlockVar], instmap_delta_bind_var(AnswerBlockVar),
        Detism, purity_semipure, Context, ReturnAnswerBlocksGoal),
    DebugArgStr = get_debug_arg_string(TableInfo),
    generate_restore_goals(ModuleInfo, OrigInstMapDelta, DebugArgStr,
        NumberedOutputVars, RestoreInstMapDeltaSrc, RestoreArgs,
        RestoreCodeStr, !VarTable),
    OutputVars = list.map(project_var, NumberedOutputVars),
    Arg = foreign_arg(AnswerBlockVar,
        yes(foreign_arg_name_mode(answer_block_name, in_mode)),
        answer_block_type, bp_native_if_possible),
    Args = [Arg],
    PredName = "table_memo_non_return_all_shortcut",
    table_call_foreign_proc(ModuleInfo, PredName,
        Args, RestoreArgs,
        instmap_delta_from_assoc_list(RestoreInstMapDeltaSrc),
        detism_det, purity_semipure, tabling_c_attributes_no_dupl,
        RestoreCodeStr, Context, ShortcutGoal),

    GoalExpr = conj(plain_conj, [ReturnAnswerBlocksGoal, ShortcutGoal]),
    set_of_var.list_to_set([RecordVar | OutputVars], NonLocals),
    goal_info_init_hide(NonLocals, OrigInstMapDelta, Detism, purity_semipure,
        Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

    % Generate a goal for restoring the output arguments from
    % an answer block in minimal model predicates without a suspension.
    %
:- pred generate_mm_restore_goal(determinism::in,
    list(var_mode_pos_method)::in, instmap_delta::in, prog_var::in,
    term.context::in, var_table::in, var_table::out, table_info::in,
    hlds_goal::out) is det.

generate_mm_restore_goal(Detism, NumberedOutputVars, OrigInstMapDelta,
        SubgoalVar, Context, !VarTable, TableInfo, Goal) :-
    ( if Detism = detism_multi then
        ReturnAllAns = "table_mm_return_all_multi"
    else if Detism = detism_non then
        ReturnAllAns = "table_mm_return_all_nondet"
    else
        unexpected($pred, "invalid determinism")
    ),
    generate_mm_restore_or_suspend_goal(ReturnAllAns, Detism, purity_semipure,
        NumberedOutputVars, OrigInstMapDelta, SubgoalVar, Context,
        !VarTable, TableInfo, Goal).

    % Generate a goal for restoring the output arguments from
    % an answer block in minimal model predicates after a suspension.
    %
:- pred generate_mm_suspend_goal(list(var_mode_pos_method)::in,
    instmap_delta::in, prog_var::in, term.context::in,
    var_table::in, var_table::out, table_info::in, hlds_goal::out) is det.

generate_mm_suspend_goal(NumberedOutputVars, OrigInstMapDelta, SubgoalVar,
        Context, !VarTable, TableInfo, Goal) :-
    generate_mm_restore_or_suspend_goal("table_mm_suspend_consumer",
        detism_non, purity_impure, NumberedOutputVars, OrigInstMapDelta,
        SubgoalVar, Context, !VarTable, TableInfo, Goal).

    % Generate a goal for restoring the output arguments from
    % an answer block in minimal model predicates. Whether the restore
    % is after a suspension depends on the arguments.
    %
:- pred generate_mm_restore_or_suspend_goal(string::in, determinism::in,
    purity::in, list(var_mode_pos_method)::in, instmap_delta::in,
    prog_var::in, term.context::in, var_table::in, var_table::out,
    table_info::in, hlds_goal::out) is det.

generate_mm_restore_or_suspend_goal(PredName, Detism, Purity,
        NumberedOutputVars, OrigInstMapDelta, SubgoalVar, Context,
        !VarTable, TableInfo, Goal) :-
    generate_new_table_var("AnswerBlock", answer_block_type, is_not_dummy_type,
        !VarTable, AnswerBlockVar),
    ModuleInfo = TableInfo ^ table_module_info,
    table_plain_call(ModuleInfo, PredName,
        [SubgoalVar, AnswerBlockVar], instmap_delta_bind_var(AnswerBlockVar),
        Detism, Purity, Context, ReturnAnswerBlocksGoal),
    DebugArgStr = get_debug_arg_string(TableInfo),
    generate_restore_goals(ModuleInfo, OrigInstMapDelta, DebugArgStr,
        NumberedOutputVars, RestoreInstMapDeltaSrc, RestoreArgs,
        RestoreCodeStr, !VarTable),
    OutputVars = list.map(project_var, NumberedOutputVars),

    Arg = foreign_arg(AnswerBlockVar,
        yes(foreign_arg_name_mode(answer_block_name, in_mode)),
        answer_block_type, bp_native_if_possible),
    Args = [Arg],
    ReturnAllPredName = "table_mm_return_all_shortcut",
    table_call_foreign_proc(ModuleInfo, ReturnAllPredName,
        Args, RestoreArgs,
        instmap_delta_from_assoc_list(RestoreInstMapDeltaSrc),
        detism_det, purity_semipure, tabling_c_attributes_no_dupl,
        RestoreCodeStr, Context, ReturnAllGoal),
    GoalExpr = conj(plain_conj, [ReturnAnswerBlocksGoal, ReturnAllGoal]),

    set_of_var.list_to_set([SubgoalVar | OutputVars], NonLocals),
    goal_info_init_hide(NonLocals, OrigInstMapDelta, Detism, Purity,
        Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

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

    % Generate a sequence of restore goals for the given variables.
    %
:- pred generate_restore_goals(module_info::in, instmap_delta::in, string::in,
    list(var_mode_pos_method(T))::in,
    assoc_list(prog_var, mer_inst)::out, list(foreign_arg)::out, string::out,
    var_table::in, var_table::out) is det.

generate_restore_goals(_, _, _, [], [], [], "", !VarTable).
generate_restore_goals(ModuleInfo, OrigInstmapDelta, DebugArgStr,
        [NumberedVar | NumberedVars], [VarInst | VarInsts], [Arg | Args],
        CodeStr ++ RestCodeStr, !VarTable) :-
    NumberedVar = var_mode_pos_method(Var, _Mode, Offset, _),
    lookup_var_type(!.VarTable, Var, VarType),
    CtorCat = classify_type(ModuleInfo, VarType),
    gen_restore_call_for_type(DebugArgStr, CtorCat, VarType, OrigInstmapDelta,
        Var, Offset, VarInst, Arg, CodeStr),
    generate_restore_goals(ModuleInfo, OrigInstmapDelta, DebugArgStr,
        NumberedVars, VarInsts, Args, RestCodeStr, !VarTable).

:- pred gen_restore_call_for_type(string::in, type_ctor_category::in,
    mer_type::in, instmap_delta::in, prog_var::in, int::in,
    pair(prog_var, mer_inst)::out, foreign_arg::out, string::out) is det.

gen_restore_call_for_type(DebugArgStr, CtorCat, Type, OrigInstmapDelta, Var,
        Offset, Var - Inst, Arg, CodeStr) :-
    Name = "restore_arg" ++ int_to_string(Offset),
    ( if type_is_io_state(Type) then
        RestoreMacroName = "MR_tbl_restore_io_state_answer",
        ArgType = Type
    else if builtin_type(CtorCat) = no then
        RestoreMacroName = "MR_tbl_restore_any_answer",
        ArgType = dummy_type_var
    else
        type_save_category(CtorCat, CatString),
        RestoreMacroName = "MR_tbl_restore_" ++ CatString ++ "_answer",
        ArgType = Type
    ),
    ( if instmap_delta_search_var(OrigInstmapDelta, Var, InstPrime) then
        Inst = InstPrime
    else
        unexpected($pred, "no inst")
    ),
    Arg = foreign_arg(Var,
        yes(foreign_arg_name_mode(Name, from_to_mode(free, Inst))),
        ArgType, bp_native_if_possible),
    CodeStr = "\t" ++ RestoreMacroName ++ "(" ++ DebugArgStr ++ ", " ++
        answer_block_name ++ ", " ++ int_to_string(Offset) ++ ", " ++
        Name ++ ");\n".

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

:- func infinite_recursion_msg = string.

infinite_recursion_msg = "detected infinite recursion".

:- func need_minimal_model_msg = string.

need_minimal_model_msg = "detected need for minimal model".

:- pred generate_error_goal(table_info::in, term.context::in, string::in,
    var_table::in, var_table::out, hlds_goal::out) is det.

generate_error_goal(TableInfo, Context, Msg, !VarTable, Goal) :-
    ModuleInfo = TableInfo ^ table_module_info,
    PredInfo = TableInfo ^ table_cur_pred_info,

    ModuleName = pred_info_module(PredInfo),
    PredName = pred_info_name(PredInfo),
    pred_info_get_orig_arity(PredInfo, PredFormArity),
    PredOrFunc = pred_info_is_pred_or_func(PredInfo),
    PredOrFuncStr = pred_or_func_to_str(PredOrFunc),
    PredNameStr = sym_name_to_string(qualified(ModuleName, PredName)),
    user_arity_pred_form_arity(PredOrFunc,
        user_arity(UserArityInt), PredFormArity),
    Message = string.format("%s in %s %s/%d",
        [s(Msg), s(PredOrFuncStr), s(PredNameStr), i(UserArityInt)]),
    make_string_const_construction_alloc(Message, "Message",
        MessageStrGoal, MessageVar, !VarTable),

    table_plain_call(ModuleInfo, "table_error",
        [MessageVar], instmap_delta_bind_no_var,
        detism_erroneous, purity_pure, Context, CallGoal),

    GoalExpr = conj(plain_conj, [MessageStrGoal, CallGoal]),
    goal_info_init_hide(set_of_var.init, instmap_delta_bind_no_var,
        detism_erroneous, purity_impure, Context, GoalInfo),
    Goal = hlds_goal(GoalExpr, GoalInfo).

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

:- pred generate_new_table_var(string::in, mer_type::in, is_dummy_type::in,
    var_table::in, var_table::out, prog_var::out) is det.

generate_new_table_var(Name, Type, IsDummy, !VarTable, Var) :-
    Entry = vte(Name, Type, IsDummy),
    add_var_entry(Entry, Var, !VarTable).

:- pred table_plain_call(module_info::in, string::in, list(prog_var)::in,
    instmap_delta::in, determinism::in, purity::in, term.context::in,
    hlds_goal::out) is det.

table_plain_call(ModuleInfo, PredName, ArgVars, InstMapDelta,
        Detism, Purity, Context, Goal) :-
    BuiltinModule = mercury_table_builtin_module,
    (
        Purity = purity_pure,
        Features0 = []
    ;
        ( Purity = purity_semipure
        ; Purity = purity_impure
        ),
        Features0 = [feature_not_impure_for_determinism]
    ),
    ( if Detism = detism_failure then
        Features = [feature_preserve_backtrack_into | Features0]
    else
        Features = Features0
    ),
    generate_plain_call(ModuleInfo, pf_predicate, BuiltinModule, PredName,
        [], ArgVars, InstMapDelta, only_mode, Detism, Purity,
        Features, Context, Goal).

:- pred table_call_foreign_proc(module_info::in, string::in,
    list(foreign_arg)::in, list(foreign_arg)::in, instmap_delta::in,
    determinism::in, purity::in, foreign_proc_attributes::in,
    string::in, term.context::in, hlds_goal::out) is det.

table_call_foreign_proc(ModuleInfo, PredName, Args, ExtraArgs, InstMapDelta,
        Detism, Purity, Attributes, Code, Context, Goal) :-
    (
        Purity = purity_pure,
        Features0 = []
    ;
        ( Purity = purity_semipure
        ; Purity = purity_impure
        ),
        Features0 = [feature_not_impure_for_determinism]
    ),
    ( if Detism = detism_failure then
        Features = [feature_preserve_backtrack_into | Features0]
    else
        Features = Features0
    ),
    BuiltinModule = mercury_table_builtin_module,
    MaybeTraceRuntimCond = no,
    generate_call_foreign_proc(ModuleInfo, pf_predicate,
        BuiltinModule, PredName, [], Args, ExtraArgs, InstMapDelta, only_mode,
        Detism, Purity, Features, Attributes,
        MaybeTraceRuntimCond, Code, Context, Goal).

:- pred append_fail(hlds_goal::in, hlds_goal::out) is det.

append_fail(Goal, GoalAndThenFail) :-
    Goal = hlds_goal(_, GoalInfo),
    NonLocals = goal_info_get_nonlocals(GoalInfo),
    Context = goal_info_get_context(GoalInfo),
    instmap_delta_init_unreachable(UnreachInstMapDelta),
    goal_info_init_hide(NonLocals, UnreachInstMapDelta, detism_failure,
        purity_impure, Context, ConjGoalInfo),
    GoalAndThenFail =
        hlds_goal(conj(plain_conj, [Goal, fail_goal]), ConjGoalInfo).

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

:- func consumer_type = mer_type.

consumer_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "ml_consumer"), 0), [], Type).

:- func generator_type = mer_type.

generator_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "ml_generator"), 0), [], Type).

:- type maybe_specified_method
    --->    msm_all_same(arg_tabling_method)
    ;       msm_specified(
                list(maybe(arg_tabling_method)),
                hidden_arg_tabling_method
            ).

:- pred get_input_output_vars(module_info::in, var_table::in,
    list(prog_var)::in, list(mer_mode)::in,
    maybe_specified_method::in, maybe_specified_method::out,
    list(var_mode_method)::out, list(var_mode_method)::out) is det.

get_input_output_vars(_, _, [], [], !MaybeSpecMethod, [], []).
get_input_output_vars(_, _, [_ | _], [], !MaybeSpecMethod, _, _) :-
    unexpected($pred, "lists not same length").
get_input_output_vars(_, _, [], [_ | _], !MaybeSpecMethod, _, _) :-
    unexpected($pred, "lists not same length").
get_input_output_vars(ModuleInfo, VarTable, [Var | Vars], [Mode | Modes],
        !MaybeSpecMethod, InVarModes, OutVarModes) :-
    lookup_var_type(VarTable, Var, Type),
    ( if mode_is_fully_input(ModuleInfo, Type, Mode) then
        get_input_output_vars(ModuleInfo, VarTable, Vars, Modes,
            !MaybeSpecMethod, InVarModes0, OutVarModes),
        (
            !.MaybeSpecMethod = msm_all_same(ArgMethod)
        ;
            !.MaybeSpecMethod =
                msm_specified(MaybeArgMethods0, HiddenArgMethod),
            ( if
                list.split_last(MaybeArgMethods0, MaybeArgMethods,
                    LastMaybeArgMethod)
            then
                (
                    LastMaybeArgMethod = yes(ArgMethod)
                ;
                    LastMaybeArgMethod = no,
                    unexpected($pred, "bad method for input var")
                ),
                !:MaybeSpecMethod = msm_specified(MaybeArgMethods,
                    HiddenArgMethod)
            else
                % We have run out of specified arg_methods, which means the
                % variable we are looking at right now is one that was added
                % by the polymorphism transformation.
                (
                    HiddenArgMethod = table_hidden_arg_value,
                    ArgMethod = arg_value
                ;
                    HiddenArgMethod = table_hidden_arg_addr,
                    ArgMethod = arg_addr
                ),
                !:MaybeSpecMethod = msm_all_same(ArgMethod)
            )
        ),
        InVarModes = [var_mode_method(Var, Mode, ArgMethod) | InVarModes0]
    else if mode_is_fully_output(ModuleInfo, Type, Mode) then
        get_input_output_vars(ModuleInfo, VarTable, Vars, Modes,
            !MaybeSpecMethod, InVarModes, OutVarModes0),
        (
            !.MaybeSpecMethod = msm_all_same(_ArgMethod)
            % The tabling methods that use answer tables always use arg_value
            % to look up computed output arguments in them. The argument of
            % all_same refers only to the treatment of input arguments.
        ;
            !.MaybeSpecMethod =
                msm_specified(MaybeArgMethods0, HiddenArgMethod),
            ( if
                list.split_last(MaybeArgMethods0, MaybeArgMethods,
                    LastMaybeArgMethod)
            then
                expect(unify(LastMaybeArgMethod, no), $pred,
                    "bad method for output var"),
                !:MaybeSpecMethod = msm_specified(MaybeArgMethods,
                    HiddenArgMethod)
            else
                % We have run out of specified arg_methods, which means the
                % variable we are looking at right now is one that was added
                % by the polymorphism transformation.
                (
                    HiddenArgMethod = table_hidden_arg_value,
                    ArgMethod = arg_value
                ;
                    HiddenArgMethod = table_hidden_arg_addr,
                    ArgMethod = arg_addr
                ),
                !:MaybeSpecMethod = msm_all_same(ArgMethod)
            )
        ),
        OutVarModes = [var_mode_method(Var, Mode, arg_value) | OutVarModes0]
    else
        % We should have caught this when we added the tabling pragma
        % to the proc_info.
        unexpected($pred, "bad var")
    ).

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

:- pred create_instmap_delta(list(hlds_goal)::in, instmap_delta::out) is det.

create_instmap_delta([], IMD) :-
    IMD = instmap_delta_bind_no_var.
create_instmap_delta([Goal | Rest], IMD) :-
    Goal = hlds_goal(_, GoalInfo),
    IMD0 = goal_info_get_instmap_delta(GoalInfo),
    create_instmap_delta(Rest, IMD1),
    instmap_delta_apply_instmap_delta(IMD0, IMD1, test_size, IMD).

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

:- pred add_proc_table_struct(pred_proc_id::in, proc_table_struct_info::in,
    proc_info::in, module_info::in, module_info::out) is det.

add_proc_table_struct(PredProcId, ProcTableStructInfo, ProcInfo,
        !ModuleInfo) :-
    module_info_get_table_struct_map(!.ModuleInfo, TableStructMap0),
    proc_info_get_table_attributes(ProcInfo, MaybeTableAttributes),
    (
        MaybeTableAttributes = yes(TableAttributes)
    ;
        MaybeTableAttributes = no,
        TableAttributes = default_memo_table_attributes
    ),
    TableStructInfo = table_struct_info(ProcTableStructInfo, TableAttributes),
    map.det_insert(PredProcId, TableStructInfo,
        TableStructMap0, TableStructMap),
    module_info_set_table_struct_map(TableStructMap, !ModuleInfo).

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

:- type var_mode_method
    --->    var_mode_method(
                % The head variable.
                prog_var,

                % The mode of the head variable.
                mer_mode,

                % For input arguments, this is the arg method to use in
                % looking up the argument in the call table. For output
                % arguments, this is the arg method to use in looking up
                % the argument in the answer table (if any).
                arg_tabling_method
            ).

:- type var_mode_pos_method == var_mode_pos_method(arg_tabling_method).

:- type var_mode_pos_method(T)
    --->    var_mode_pos_method(
                % The head variable.
                prog_var,

                % The mode of the head variable.
                mer_mode,

                % The offset of the head variable in the answer block;
                % the first slot is at offset 0.
                int,

                % For input arguments, this is the arg method to use in
                % looking up the argument in the call table. For output
                % arguments, this is the arg method to use in looking up
                % the argument in the answer table (if any), which for now
                % is always arg_value.
                %
                % When T is unit, there is no info about how to look up
                % the variable in a call or return table. This is useful
                % for recording the structure of answer blocks and sequences
                % of arguments for I/O tabling.
                T
            ).

:- func project_var(var_mode_pos_method(T)) = prog_var.

project_var(var_mode_pos_method(Var, _, _, _)) = Var.

:- func project_var_pos(var_mode_pos_method(T)) = pair(prog_var, int).

project_var_pos(var_mode_pos_method(Var, _, Pos, _)) = Var - Pos.

:- func project_var_init_inst(module_info, var_mode_pos_method(T)) =
    pair(prog_var, mer_inst).

project_var_init_inst(ModuleInfo, var_mode_pos_method(Var, Mode, _, _))
        = Var - Inst :-
    mode_get_insts(ModuleInfo, Mode, Inst, _).

:- func project_mode(var_mode_pos_method(T)) = mer_mode.

project_mode(var_mode_pos_method(_, Mode, _, _)) = Mode.

:- func project_out_arg_method(var_mode_pos_method) =
    var_mode_pos_method(unit).

project_out_arg_method(var_mode_pos_method(Var, Mode, Pos, _)) =
    var_mode_pos_method(Var, Mode, Pos, unit).

:- func project_out_pos(var_mode_pos_method) = var_mode_method.

project_out_pos(var_mode_pos_method(Var, Mode, _Pos, ArgMethod)) =
    var_mode_method(Var, Mode, ArgMethod).

:- pred allocate_slot_numbers(list(var_mode_method)::in,
    int::in, list(var_mode_pos_method)::out) is det.

allocate_slot_numbers([], _, []).
allocate_slot_numbers([var_mode_method(Var, Mode, ArgMethod) | VarModes],
        Offset0, [VarModePos | VarModePoss]) :-
    VarModePos = var_mode_pos_method(Var, Mode, Offset0, ArgMethod),
    allocate_slot_numbers(VarModes, Offset0 + 1, VarModePoss).

:- pred allocate_plain_slot_numbers(assoc_list(prog_var, mer_mode)::in,
    int::in, list(var_mode_pos_method(unit))::out) is det.

allocate_plain_slot_numbers([], _, []).
allocate_plain_slot_numbers([Var - Mode | VarModes],
        Offset0, [VarModePos | VarModePoss]) :-
    VarModePos = var_mode_pos_method(Var, Mode, Offset0, unit),
    allocate_plain_slot_numbers(VarModes, Offset0 + 1, VarModePoss).

:- pred reallocate_slot_numbers(list(var_mode_pos_method(T))::in, int::in,
    list(var_mode_pos_method(T))::out) is det.

reallocate_slot_numbers([], _, []).
reallocate_slot_numbers([VarModePos0 | VarModes], Offset0,
        [VarModePos | VarModePoss]) :-
    VarModePos0 = var_mode_pos_method(Var, Mode, _, ArgMethod),
    VarModePos = var_mode_pos_method(Var, Mode, Offset0, ArgMethod),
    reallocate_slot_numbers(VarModes, Offset0 + 1, VarModePoss).

:- pred var_belong_to_list(list(prog_var)::in, var_mode_pos_method(T)::in)
    is semidet.

var_belong_to_list(List, var_mode_pos_method(Var, _, _, _)) :-
    list.member(Var, List).

:- pred goal_info_init_hide(set_of_progvar::in, instmap_delta::in,
    determinism::in, purity::in, prog_context::in, hlds_goal_info::out)
    is det.

goal_info_init_hide(NonLocals, InstmapDelta, Detism, Purity, Context,
        GoalInfo) :-
    goal_info_init(NonLocals, InstmapDelta, Detism, Purity, Context,
        GoalInfo0),
    goal_info_add_feature(feature_hide_debug_event, GoalInfo0, GoalInfo).

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

    % For backward compatibility, we treat type_info_type as user_type.
    % This used to make the tabling of type_infos more expensive than
    % necessary, since we essentially tabled the information in the type_info
    % twice, once by tabling the type represented by the type_info (since this
    % was the value of the type argument of the type constructor
    % private_builtin.type_info/1), and then tabling the type_info itself.
    % However, since we made type_info have arity zero, this overhead
    % should be gone.
    %
:- func builtin_type(type_ctor_category) = bool.

builtin_type(CtorCat) = Builtin :-
    (
        ( CtorCat = ctor_cat_builtin(_)
        ; CtorCat = ctor_cat_void
        ; CtorCat = ctor_cat_system(cat_system_type_ctor_info)
        ; CtorCat = ctor_cat_system(cat_system_typeclass_info)
        ; CtorCat = ctor_cat_system(cat_system_base_typeclass_info)
        ),
        Builtin = yes
    ;
        ( CtorCat = ctor_cat_user(_)
        ; CtorCat = ctor_cat_enum(_)
        ; CtorCat = ctor_cat_system(cat_system_type_info)
        ; CtorCat = ctor_cat_variable
        ; CtorCat = ctor_cat_tuple
        ; CtorCat = ctor_cat_builtin_dummy
        ; CtorCat = ctor_cat_higher_order
        ),
        Builtin = no
    ).

    % Figure out which save and restore predicates in library/table_builtin.m
    % we need to use for values of types belonging the type category given by
    % the first argument. The returned value replaces CAT in
    % table_save_CAT_answer and table_restore_CAT_answer.
    %
:- pred type_save_category(type_ctor_category::in, string::out) is det.

type_save_category(CtorCat, Name) :-
    (
        CtorCat = ctor_cat_enum(cat_enum_mercury),
        Name = "enum"
    ;
        CtorCat = ctor_cat_enum(cat_enum_foreign),
        sorry($pred, "tabling and foreign enumerations NYI.")
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_int)),
        Name = "int"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_uint)),
        Name = "uint"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_int8)),
        Name = "int8"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_uint8)),
        Name = "uint8"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_int16)),
        Name = "int16"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_uint16)),
        Name = "uint16"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_int32)),
        Name = "int32"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_uint32)),
        Name = "uint32"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_int64)),
        Name = "int64"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_int(int_type_uint64)),
        Name = "uint64"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_float),
        Name = "float"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_char),
        Name = "char"
    ;
        CtorCat = ctor_cat_builtin(cat_builtin_string),
        Name = "string"
    ;
        CtorCat = ctor_cat_higher_order,
        Name = "pred"
    ;
        % Could do better.
        ( CtorCat = ctor_cat_user(_)
        ; CtorCat = ctor_cat_variable
        ; CtorCat = ctor_cat_system(cat_system_type_info)
        ),
        Name = "any"
    ;
        CtorCat = ctor_cat_tuple,
        Name = "any"
    ;
        ( CtorCat = ctor_cat_system(cat_system_type_ctor_info)
        ; CtorCat = ctor_cat_system(cat_system_typeclass_info)
        ; CtorCat = ctor_cat_system(cat_system_base_typeclass_info)
        ; CtorCat = ctor_cat_builtin_dummy
        ; CtorCat = ctor_cat_void
        ),
        unexpected($pred, "unexpected category")
    ).

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

:- pred get_enum_max_int_tag(type_table::in, type_ctor::in, int::out) is det.

get_enum_max_int_tag(TypeTable, TypeCtor, MaxIntTag) :-
    lookup_type_ctor_defn(TypeTable, TypeCtor, TypeDefn),
    hlds_data.get_type_defn_body(TypeDefn, TypeBody),
    ( if
        TypeBody = hlds_du_type(TypeBodyDu),
        TypeBodyDu = type_body_du(_Ctors, MaybeSuperType, MaybeCanonical,
            MaybeRepn, _MaybeForeign),
        MaybeCanonical = canon,
        MaybeRepn = yes(Repn),
        Repn ^ dur_kind = du_type_kind_mercury_enum
    then
        CtorRepns = Repn ^ dur_ctor_repns,
        (
            MaybeSuperType = not_a_subtype,
            list.det_last(CtorRepns, LastCtorRepn),
            max_enum_int_tag(LastCtorRepn, 0, MaxIntTag)
        ;
            MaybeSuperType = subtype_of(_),
            % Subtype enums do not necessary use all values from 0 to
            % MaxIntTag, so this will create a trie node that may be larger
            % than necessary. We could subtract the lowest possible enum value
            % to reduce the range, or switch to a different step type if the
            % range of values is sparsely populated.
            list.foldl(max_enum_int_tag, CtorRepns, 0, MaxIntTag)
        )
    else
        unexpected($pred, "enum type is not du_type?")
    ).

:- pred max_enum_int_tag(constructor_repn::in, int::in, int::out) is det.

max_enum_int_tag(CtorRepn, !MaxIntTag) :-
    ConsTag = CtorRepn ^ cr_tag,
    ( if ConsTag = int_tag(int_tag_int(IntTagVal)) then
        !:MaxIntTag = int.max(!.MaxIntTag, IntTagVal)
    else
        unexpected($pred, "enum has non-int tag")
    ).

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

:- func get_debug_arg_string(table_info) = string.

get_debug_arg_string(TableInfo) = DebugArgStr :-
    ModuleInfo = TableInfo ^ table_module_info,
    module_info_get_globals(ModuleInfo, Globals),
    globals.lookup_bool_option(Globals, table_debug, TableDebug),
    (
        TableDebug = yes,
        DebugArgStr = "MR_TRUE"
    ;
        TableDebug = no,
        DebugArgStr = "MR_FALSE"
    ).

:- func get_back_arg_string(table_info) = string.

get_back_arg_string(TableInfo) = BackArgStr :-
    ProcInfo = TableInfo ^ table_cur_proc_info,
    proc_info_get_table_attributes(ProcInfo, MaybeAttributes),
    (
        MaybeAttributes = yes(Attributes),
        MaybeSizeLimit = Attributes ^ table_attr_size_limit,
        (
            MaybeSizeLimit = yes(_),
            BackArgStr = "MR_TRUE"
        ;
            MaybeSizeLimit = no,
            BackArgStr = "MR_FALSE"
        )
    ;
        MaybeAttributes = no,
        BackArgStr = "MR_FALSE"
    ).

:- type call_or_answer_table
    --->    call_table
    ;       answer_table.

:- func stats_ref(table_attr_statistics, call_or_answer_table) = maybe(string).

stats_ref(Statistics, Kind) = MaybeStatsRef :-
    (
        Statistics = table_do_not_gather_statistics,
        MaybeStatsRef = no
    ;
        Statistics = table_gather_statistics,
        (
            Kind = call_table,
            KindStr = "MR_TABLE_CALL_TABLE"
        ;
            Kind = answer_table,
            KindStr = "MR_TABLE_ANSWER_TABLE"
        ),
        StatsRef = proc_table_info_name ++ "->MR_pt_stats" ++
            "[" ++ KindStr ++ "][MR_TABLE_STATS_CURR]",
        MaybeStatsRef = yes(StatsRef)
    ).

:- func maybe_step_stats_arg_addr(maybe(string), int) = string.

maybe_step_stats_arg_addr(MaybeStatsRef, SeqNum) = ArgStr :-
    (
        MaybeStatsRef = no,
        ArgStr = "NULL"
    ;
        MaybeStatsRef = yes(StatsRef),
        ArgStr = "&(" ++ step_stats_arg_addr(StatsRef, SeqNum) ++ ")"
    ).

:- func step_stats_arg_addr(string, int) = string.

step_stats_arg_addr(StatsRef, SeqNum) = ArgStr :-
    ArgStr = StatsRef ++ ".MR_ts_steps" ++ "[" ++ int_to_string(SeqNum) ++ "]".

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

:- pred table_gen_make_type_info_var(mer_type::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    prog_var::out, list(hlds_goal)::out) is det.

table_gen_make_type_info_var(Type, Context, !VarTable, !TableInfo,
        TypeInfoVar, TypeInfoGoals) :-
    table_gen_make_type_info_vars([Type], Context, !VarTable, !TableInfo,
        TypeInfoVars, TypeInfoGoals),
    ( if TypeInfoVars = [TypeInfoVar0] then
        TypeInfoVar = TypeInfoVar0
    else
        unexpected($pred, "list length != 1")
    ).

:- pred table_gen_make_type_info_vars(list(mer_type)::in, term.context::in,
    var_table::in, var_table::out, table_info::in, table_info::out,
    list(prog_var)::out, list(hlds_goal)::out) is det.

table_gen_make_type_info_vars(Types, Context, !VarTable, !TableInfo,
        TypeInfoVars, TypeInfoGoals) :-
    % Extract the information from table_info.
    table_info_extract(!.TableInfo, ModuleInfo0, PredInfo0, ProcInfo0),

    % Put the var_table back in the proc_info.
    proc_info_set_var_table(!.VarTable, ProcInfo0, ProcInfo1),

    % Generate the code that creates the type_infos.
    polymorphism_make_type_info_vars_mi(Types, Context,
        TypeInfoVars, TypeInfoGoals, ModuleInfo0, ModuleInfo,
        PredInfo0, PredInfo, ProcInfo1, ProcInfo),

    % Get the new var_table from the proc_info.
    proc_info_get_var_table(ProcInfo, !:VarTable),

    % Put the new module_info, pred_info, and proc_info back in the table_info.
    table_info_init(ModuleInfo, PredInfo, ProcInfo, !:TableInfo).

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

:- pred var_mode_pos_is_io_state(var_table::in, var_mode_pos_method::in)
    is semidet.

var_mode_pos_is_io_state(VarTable, VarModePosMethod) :-
    var_is_io_state(VarTable, project_var(VarModePosMethod)).

:- pred var_mode_is_io_state(var_table::in, pair(prog_var, mer_mode)::in)
    is semidet.

var_mode_is_io_state(VarTable, Var - _) :-
    var_is_io_state(VarTable, Var).

:- pred var_is_io_state(var_table::in, prog_var::in) is semidet.

var_is_io_state(VarTable, Var) :-
    lookup_var_type(VarTable, Var, VarType),
    type_is_io_state(VarType).

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

:- func tabling_c_attributes_dupl = foreign_proc_attributes.

tabling_c_attributes_dupl = Attrs :-
    Attrs0 = default_attributes(lang_c),
    set_may_call_mercury(proc_will_not_call_mercury, Attrs0, Attrs1),
    set_affects_liveness(proc_does_not_affect_liveness, Attrs1, Attrs2),
    set_may_duplicate(yes(proc_may_duplicate), Attrs2, Attrs).

:- func tabling_c_attributes_no_dupl = foreign_proc_attributes.

tabling_c_attributes_no_dupl = Attrs :-
    Attrs0 = default_attributes(lang_c),
    set_may_call_mercury(proc_will_not_call_mercury, Attrs0, Attrs1),
    set_affects_liveness(proc_does_not_affect_liveness, Attrs1, Attrs).

:- func make_generator_c_attributes = foreign_proc_attributes.

make_generator_c_attributes = Attrs :-
    Attrs0 = default_attributes(lang_c),
    set_may_call_mercury(proc_may_call_mercury, Attrs0, Attrs).

:- func dummy_type_var = mer_type.

dummy_type_var = Type :-
    % XXX This code looks like it will generate type variables that
    % will collide with actual type variables of the current procedure,
    % if it has any.
    varset.init(DummyTVarSet0),
    varset.new_var(DummyTVar, DummyTVarSet0, _),
    Type = type_variable(DummyTVar, kind_star).

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

:- func loop_inactive_cons_id = cons_id.
:- func loop_active_cons_id = cons_id.

loop_inactive_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "loop_inactive"),
    TypeCtor = loop_status_type_ctor.
loop_active_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "loop_active"),
    TypeCtor = loop_status_type_ctor.

:- func memo_det_inactive_cons_id = cons_id.
:- func memo_det_active_cons_id = cons_id.
:- func memo_det_succeeded_cons_id = cons_id.

memo_det_inactive_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_det_inactive"),
    TypeCtor = memo_det_status_type_ctor.
memo_det_active_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_det_active"),
    TypeCtor = memo_det_status_type_ctor.
memo_det_succeeded_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_det_succeeded"),
    TypeCtor = memo_det_status_type_ctor.

:- func memo_semi_inactive_cons_id = cons_id.
:- func memo_semi_active_cons_id = cons_id.
:- func memo_semi_succeeded_cons_id = cons_id.
:- func memo_semi_failed_cons_id = cons_id.

memo_semi_inactive_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_semi_inactive"),
    TypeCtor = memo_semi_status_type_ctor.
memo_semi_active_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_semi_active"),
    TypeCtor = memo_semi_status_type_ctor.
memo_semi_succeeded_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_semi_succeeded"),
    TypeCtor = memo_semi_status_type_ctor.
memo_semi_failed_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_semi_failed"),
    TypeCtor = memo_semi_status_type_ctor.

:- func memo_non_inactive_cons_id = cons_id.
:- func memo_non_active_cons_id = cons_id.
:- func memo_non_incomplete_cons_id = cons_id.
:- func memo_non_complete_cons_id = cons_id.

memo_non_inactive_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_non_inactive"),
    TypeCtor = memo_non_status_type_ctor.
memo_non_active_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_non_active"),
    TypeCtor = memo_non_status_type_ctor.
memo_non_incomplete_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_non_incomplete"),
    TypeCtor = memo_non_status_type_ctor.
memo_non_complete_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "memo_non_complete"),
    TypeCtor = memo_non_status_type_ctor.

:- func mm_inactive_cons_id = cons_id.
:- func mm_active_cons_id = cons_id.
:- func mm_complete_cons_id = cons_id.

mm_inactive_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "mm_inactive"),
    TypeCtor = mm_status_type_ctor.
mm_active_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "mm_active"),
    TypeCtor = mm_status_type_ctor.
mm_complete_cons_id = du_data_ctor(du_ctor(SymName, 0, TypeCtor)) :-
    SymName = qualified(mercury_table_builtin_module, "mm_complete"),
    TypeCtor = mm_status_type_ctor.

:- func loop_status_type_ctor = type_ctor.

loop_status_type_ctor = TypeCtor :-
    TypeModule = mercury_table_builtin_module,
    TypeSymName = qualified(TypeModule, "loop_status"),
    TypeCtor = type_ctor(TypeSymName, 0).

:- func memo_det_status_type_ctor = type_ctor.

memo_det_status_type_ctor = TypeCtor :-
    TypeModule = mercury_table_builtin_module,
    TypeSymName = qualified(TypeModule, "memo_det_status"),
    TypeCtor = type_ctor(TypeSymName, 0).

:- func memo_semi_status_type_ctor = type_ctor.

memo_semi_status_type_ctor = TypeCtor :-
    TypeModule = mercury_table_builtin_module,
    TypeSymName = qualified(TypeModule, "memo_semi_status"),
    TypeCtor = type_ctor(TypeSymName, 0).

:- func memo_non_status_type_ctor = type_ctor.

memo_non_status_type_ctor = TypeCtor :-
    TypeModule = mercury_table_builtin_module,
    TypeSymName = qualified(TypeModule, "memo_non_status"),
    TypeCtor = type_ctor(TypeSymName, 0).

:- func mm_status_type_ctor = type_ctor.

mm_status_type_ctor = TypeCtor :-
    TypeModule = mercury_table_builtin_module,
    TypeSymName = qualified(TypeModule, "mm_status"),
    TypeCtor = type_ctor(TypeSymName, 0).

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

:- func proc_table_info_type = mer_type.
:- func trie_node_type = mer_type.
:- func memo_non_record_type = mer_type.
:- func subgoal_type = mer_type.
:- func answer_block_type = mer_type.
:- func loop_status_type = mer_type.
:- func memo_det_status_type = mer_type.
:- func memo_semi_status_type = mer_type.
:- func memo_non_status_type = mer_type.
:- func mm_status_type = mer_type.

proc_table_info_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "ml_proc_table_info"), 0),
        [], Type).

trie_node_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "ml_trie_node"), 0), [], Type).

memo_non_record_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "memo_non_record"), 0), [], Type).

subgoal_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "ml_subgoal"), 0), [], Type).

answer_block_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "ml_answer_block"), 0), [], Type).

loop_status_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "loop_status"), 0), [], Type).

memo_det_status_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "memo_det_status"), 0), [], Type).

memo_semi_status_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "memo_semi_status"), 0), [], Type).

memo_non_status_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "memo_non_status"), 0), [], Type).

mm_status_type = Type :-
    TB = mercury_table_builtin_module,
    construct_type(type_ctor(qualified(TB, "mm_status"), 0), [], Type).

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

:- func proc_table_info_name = string.
:- func cur_table_node_name = string.
:- func next_table_node_name = string.
:- func table_tip_node_name = string.
:- func base_name = string.
:- func memo_non_record_name = string.
:- func subgoal_name = string.
:- func status_name = string.
:- func answer_block_name = string.
:- func success_indicator_name = string.
:- func arg_name(int) = string.
:- func num_input_args_name = string.
:- func pred_name_var_name = string.
:- func answer_table_name = string.
:- func consumer_name = string.
:- func generator_name = string.
:- func generator_pred_name = string.
:- func returning_generator_locn = string.

:- pragma consider_used(func(table_tip_node_name/0)).
:- pragma consider_used(func(num_input_args_name/0)).
:- pragma consider_used(func(pred_name_var_name/0)).
:- pragma consider_used(func(answer_table_name/0)).

proc_table_info_name = "proc_table_info".
cur_table_node_name = "cur_node".
next_table_node_name = "next_node".
table_tip_node_name = "table_tip".
base_name = "base".
memo_non_record_name = "record".
subgoal_name = "subgoal".
status_name = "status".
answer_block_name = "answerblock".
success_indicator_name = "SUCCESS_INDICATOR".
arg_name(VarSeqNum) = "arg" ++ int_to_string(VarSeqNum).
num_input_args_name = "num_input_args".
pred_name_var_name = "pred_name".
answer_table_name = "answer_table".
consumer_name = "consumer".
generator_name = "generator".
generator_pred_name = "generator_pred".
returning_generator_locn = "MR_mmos_returning_generator".

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

:- type table_info
    --->    table_info(
                table_module_info           :: module_info,
                table_cur_pred_info         :: pred_info,
                table_cur_proc_info         :: proc_info
            ).

:- pred table_info_init(module_info::in, pred_info::in, proc_info::in,
    table_info::out) is det.

table_info_init(ModuleInfo, PredInfo, ProcInfo, TableInfo) :-
    TableInfo = table_info(ModuleInfo, PredInfo, ProcInfo).

:- pred table_info_extract(table_info::in,
        module_info::out, pred_info::out, proc_info::out) is det.

table_info_extract(TableInfo, ModuleInfo, PredInfo, ProcInfo) :-
    TableInfo = table_info(ModuleInfo, PredInfo, ProcInfo).

%---------------------------------------------------------------------------%
:- end_module transform_hlds.table_gen.
%---------------------------------------------------------------------------%
