%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1994-2011 The University of Melbourne.
% Copyright (C) 2015, 2017-2018, 2021-2024 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: ml_string_switch.m.
% Authors: fjh, zs.
%
% For switches on strings, we can generate
%
% - a trie;
% - a hash table using open addressing to resolve hash conflicts; or
% - a sorted table for binary search.
%
% The hash table has a higher startup cost than binary search, but should use
% fewer comparisons, so it is preferred for bigger tables. The trie approach
% does not need any startup code and examines each character just once,
% so it does the least work, but it does do a hard-to-predict jump
% for each character in the trie (which usually *won't* be all the characters
% in the string being switched on).
%
% All of these techniques are implemented both for switches in which
% each arm executes code (we call these the "jump" versions, since the task
% of the indexing method is to decide which piece of code to jump to),
% and for switches in which each arm looks up the data to return in tables
% (we call these the "lookup" versions). The lookup versions themselves
% come in two distinct flavours: those in which each arm has at most
% one solution, and those in which some arms have more than one solution.
%
% WARNING: the code here is quite similar to the code in string_switch.m.
% Any changes here may require similar changes there, and vice versa.
%
%---------------------------------------------------------------------------%

:- module ml_backend.ml_string_switch.
:- interface.

:- import_module hlds.
:- import_module hlds.code_model.
:- import_module hlds.hlds_goal.
:- import_module ml_backend.ml_gen_info.
:- import_module ml_backend.ml_lookup_switch.
:- import_module ml_backend.mlds.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.

:- import_module list.

:- pred ml_generate_string_trie_jump_switch(mlds_rval::in,
    list(tagged_case)::in, code_model::in, can_fail::in, packed_word_map::in,
    prog_context::in, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

:- pred ml_generate_string_trie_lookup_switch(mlds_rval::in,
    list(tagged_case)::in, ml_lookup_switch_info::in,
    code_model::in, can_fail::in, prog_context::in,
    list(mlds_stmt)::out, ml_gen_info::out) is det.

:- pred ml_generate_string_hash_jump_switch(mlds_rval::in,
    list(tagged_case)::in, code_model::in, can_fail::in, packed_word_map::in,
    prog_context::in, list(mlds_local_var_defn)::out, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

:- pred ml_generate_string_hash_lookup_switch(mlds_rval::in,
    list(tagged_case)::in, ml_lookup_switch_info::in,
    code_model::in, can_fail::in, prog_context::in,
    list(mlds_local_var_defn)::out, list(mlds_stmt)::out,
    ml_gen_info::out) is det.

:- pred ml_generate_string_binary_jump_switch(mlds_rval::in,
    list(tagged_case)::in, code_model::in, can_fail::in, packed_word_map::in,
    prog_context::in, list(mlds_local_var_defn)::out, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

:- pred ml_generate_string_binary_lookup_switch(mlds_rval::in,
    list(tagged_case)::in, ml_lookup_switch_info::in,
    code_model::in, can_fail::in, prog_context::in,
    list(mlds_local_var_defn)::out, list(mlds_stmt)::out,
    ml_gen_info::out) is det.

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

:- implementation.

:- import_module backend_libs.
:- import_module backend_libs.builtin_ops.
:- import_module backend_libs.lookup_switch_util.
:- import_module backend_libs.string_encoding.
:- import_module backend_libs.string_switch_util.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_module.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module ml_backend.ml_code_gen.
:- import_module ml_backend.ml_code_util.
:- import_module ml_backend.ml_global_data.
:- import_module ml_backend.ml_simplify_switch.
:- import_module ml_backend.ml_target_util.
:- import_module ml_backend.ml_util.
:- import_module parse_tree.builtin_lib_types.

:- import_module assoc_list.
:- import_module bool.
:- import_module cord.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module string.
:- import_module unit.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of jump tries.
%

ml_generate_string_trie_jump_switch(VarRval, TaggedCases, CodeModel, CanFail,
        EntryPackedArgsMap, Context, Stmts, !Info) :-
    ml_gen_tagged_case_codes_for_string_switch(CodeModel, EntryPackedArgsMap,
        TaggedCases, map.init, CodeMap, [], ReachableConstVarMaps, !Info),
    ml_gen_record_consensus_const_var_map(ReachableConstVarMaps, !Info),
    ml_create_nested_switch_trie(TaggedCases, Context, VarRval, MaxCaseNum,
        CaseNumVarLval, CaseNumVarDefn,
        InitCaseNumVarStmt, GetCaseNumSwitchStmt, !Info),

    map.to_assoc_list(CodeMap, CodeCases),
    ml_generate_arms_of_switch_on_case_id(CodeCases,
        cord.init, CaseNumSwitchArmsCord),
    CaseNumSwitchArms = cord.list(CaseNumSwitchArmsCord),
    ml_gen_default_of_switch_on_case_id(CodeModel, CanFail, Context,
        CaseNumDefault, !Info),
    CaseNumSwitchRange = mlds_switch_range(0, MaxCaseNum),
    CaseSwitchStmt = ml_stmt_switch(mlds_builtin_type_int(int_type_int),
        ml_lval(CaseNumVarLval), CaseNumSwitchRange, CaseNumSwitchArms,
        CaseNumDefault, Context),

    Stmt = ml_stmt_block([CaseNumVarDefn], [],
        [InitCaseNumVarStmt, GetCaseNumSwitchStmt, CaseSwitchStmt], Context),
    Stmts = [Stmt].

:- pred ml_gen_default_of_switch_on_case_id(code_model::in, can_fail::in,
    prog_context::in, mlds_switch_default::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_default_of_switch_on_case_id(CodeModel, CanFail, Context,
        CaseNumDefault, !Info) :-
    (
        CanFail = cannot_fail,
        CaseNumDefault = default_is_unreachable
    ;
        CanFail = can_fail,
        ml_gen_maybe_switch_failure(CodeModel, CanFail, Context, FailStmts,
            !Info),
        (
            FailStmts = [],
            (
                CodeModel = model_det,
                % For model_det switches, the default must be unreachable.
                CaseNumDefault = default_is_unreachable
            ;
                CodeModel = model_semi,
                % For model_semi switches, the default should contain at least
                % an assignment of FALSE to the "succeeded" flag.
                unexpected($pred, "failure does not assign to succeeded")
            ;
                CodeModel = model_non,
                FailStmt = ml_stmt_block([], [], [], Context),
                CaseNumDefault = default_case(FailStmt)
            )
        ;
            (
                FailStmts = [FailStmt]
            ;
                FailStmts = [_, _ | _],
                FailStmt = ml_stmt_block([], [], FailStmts, Context)
            ),
            CaseNumDefault = default_case(FailStmt)
        )
    ).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of lookup tries.
%

ml_generate_string_trie_lookup_switch(VarRval, TaggedCases, LookupSwitchInfo,
        CodeModel, CanFail, Context, Stmts, !:Info) :-
    LookupSwitchInfo = ml_lookup_switch_info(CaseIdConsts, OutVars, OutTypes,
        !:Info),
    ml_create_nested_switch_trie(TaggedCases, Context, VarRval, MaxCaseNum,
        CaseNumVarLval, CaseNumVarDefn,
        InitCaseNumVarStmt, GetCaseNumSwitchStmt, !Info),
    (
        CaseIdConsts = all_one_soln(CaseIdValueMap),
        map.to_assoc_list(CaseIdValueMap, CaseIdValues),
        ml_generate_string_trie_simple_lookup_switch(MaxCaseNum,
            CaseNumVarLval, CaseNumVarDefn,
            InitCaseNumVarStmt, GetCaseNumSwitchStmt,
            CaseIdValues, OutVars, OutTypes, CodeModel, CanFail, Context,
            Stmts, !Info)
    ;
        CaseIdConsts = some_several_solns(CaseIdSolnMap, _Unit),
        expect(unify(CodeModel, model_non), $pred, "CodeModel != model_non"),
        map.to_assoc_list(CaseIdSolnMap, CaseIdSolns),
        ml_generate_string_trie_several_soln_lookup_switch(MaxCaseNum,
            CaseNumVarLval, CaseNumVarDefn,
            InitCaseNumVarStmt, GetCaseNumSwitchStmt,
            CaseIdSolns, OutVars, OutTypes, CanFail, Context,
            Stmts, !Info)
    ).

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

:- pred ml_generate_string_trie_simple_lookup_switch(int::in,
    mlds_lval::in, mlds_local_var_defn::in, mlds_stmt::in, mlds_stmt::in,
    assoc_list(case_id, list(mlds_rval))::in,
    list(prog_var)::in, list(mlds_type)::in,
    code_model::in, can_fail::in, prog_context::in,
    list(mlds_stmt)::out, ml_gen_info::in, ml_gen_info::out) is det.

ml_generate_string_trie_simple_lookup_switch(MaxCaseNum,
        CaseNumVarLval, CaseNumVarDefn,
        InitCaseNumVarStmt, GetCaseNumSwitchStmt,
        CaseIdValues, OutVars, OutTypes, CodeModel, CanFail, Context,
        Stmts, !Info) :-
    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    ml_gen_info_get_target(!.Info, Target),

    (
        OutTypes = [_ | _],
        ml_gen_info_get_global_data(!.Info, GlobalData0),
        ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
            OutTypes, StructTypeNum, StructType, OutFieldIds,
            GlobalData0, GlobalData1),
        ml_gen_string_trie_simple_lookup_slots(StructType, CaseIdValues,
            0, AfterLastCaseNum, cord.init, RowInitializersCord),
        expect(unify(MaxCaseNum + 1, AfterLastCaseNum), $pred,
            "MaxCaseNum + 1 != AfterLastCaseNum"),
        RowInitializers = cord.list(RowInitializersCord),
        ml_gen_static_vector_defn(MLDS_ModuleName, StructTypeNum,
            RowInitializers, VectorCommon, GlobalData1, GlobalData),
        ml_gen_info_set_global_data(GlobalData, !Info),

        ml_generate_field_assigns(OutVars, OutTypes, OutFieldIds, VectorCommon,
            StructType, ml_lval(CaseNumVarLval), Context,
            FieldAssignStmts, !Info)
    ;
        OutTypes = [],
        FieldAssignStmts = []
    ),

    FoundMatchComment = "we found a match; look up the results",
    FoundMatchCommentStmt =
        ml_stmt_atomic(comment(FoundMatchComment), Context),
    CommentedFieldAssignStmts = [FoundMatchCommentStmt | FieldAssignStmts],
    (
        CodeModel = model_det,
        LookupStmts = CommentedFieldAssignStmts
    ;
        CodeModel = model_semi,
        ml_gen_set_success(ml_const(mlconst_true), Context, SetSuccessTrueStmt,
            !Info),
        LookupStmts = CommentedFieldAssignStmts ++ [SetSuccessTrueStmt]
    ;
        CodeModel = model_non,
        unexpected($pred, "model_non")
    ),
    LookupStmt = ml_stmt_block([], [], LookupStmts, Context),

    ml_gen_maybe_switch_failure(CodeModel, CanFail, Context, FailStmts,
        !Info),
    (
        FailStmts = [],
        ResultStmt = LookupStmt
    ;
        (
            FailStmts = [FailStmt]
        ;
            FailStmts = [_, _ | _],
            FailStmt = ml_stmt_block([], [], FailStmts, Context)
        ),
        IsCaseNumNegCond = ml_binop(int_cmp(int_type_int, lt),
            ml_lval(CaseNumVarLval), ml_const(mlconst_int(0))),
        ResultStmt = ml_stmt_if_then_else(IsCaseNumNegCond, FailStmt,
            yes(LookupStmt), Context)
    ),

    Stmt = ml_stmt_block([CaseNumVarDefn], [],
        [InitCaseNumVarStmt, GetCaseNumSwitchStmt, ResultStmt], Context),
    Stmts = [Stmt].

:- pred ml_gen_string_trie_simple_lookup_slots(mlds_type::in,
    assoc_list(case_id, list(mlds_rval))::in, int::in, int::out,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out) is det.

ml_gen_string_trie_simple_lookup_slots(_StructType, [],
        !CurCaseNum, !RowInitializersCord).
ml_gen_string_trie_simple_lookup_slots(StructType,
        [CaseIdValue | CaseIdValues], !CurCaseNum, !RowInitializersCord) :-
    CaseIdValue = CaseId - OutRvals,
    CaseId = case_id(CaseIdNum),
    expect(unify(CaseIdNum, !.CurCaseNum), $pred, "CaseIdNum != !.CurCaseNum"),
    OutInitializers = list.map(wrap_init_obj, OutRvals),
    RowInitializer = init_struct(StructType, OutInitializers),
    cord.snoc(RowInitializer, !RowInitializersCord),
    !:CurCaseNum = !.CurCaseNum + 1,
    ml_gen_string_trie_simple_lookup_slots(StructType, CaseIdValues,
        !CurCaseNum, !RowInitializersCord).

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

:- pred ml_generate_string_trie_several_soln_lookup_switch(int::in,
    mlds_lval::in, mlds_local_var_defn::in, mlds_stmt::in, mlds_stmt::in,
    assoc_list(case_id, soln_consts(mlds_rval))::in,
    list(prog_var)::in, list(mlds_type)::in, can_fail::in, prog_context::in,
    list(mlds_stmt)::out, ml_gen_info::in, ml_gen_info::out) is det.

ml_generate_string_trie_several_soln_lookup_switch(MaxCaseNum,
        CaseNumVarLval, CaseNumVarDefn,
        InitCaseNumVarStmt, GetCaseNumSwitchStmt,
        CaseIdSolns, OutVars, OutTypes, CanFail, Context, Stmts, !Info) :-
    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    ml_gen_info_get_target(!.Info, Target),

    ml_gen_info_get_global_data(!.Info, GlobalData0),
    MLDS_IntType = mlds_builtin_type_int(int_type_int),
    FirstSolnFieldTypes = [MLDS_IntType, MLDS_IntType | OutTypes],
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        FirstSolnFieldTypes, FirstSolnStructTypeNum, FirstSolnStructType,
        FirstSolnFieldIds, GlobalData0, GlobalData1),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        OutTypes, LaterSolnStructTypeNum, LaterSolnStructType,
        LaterSolnOutFieldIds, GlobalData1, GlobalData2),
    ( if
        FirstSolnFieldIds =
            [NumLaterSolnsFieldIdPrime, FirstLaterSolnRowFieldIdPrime |
            FirstSolnOutFieldIdsPrime]
    then
        NumLaterSolnsFieldId = NumLaterSolnsFieldIdPrime,
        FirstLaterSolnRowFieldId = FirstLaterSolnRowFieldIdPrime,
        FirstSolnOutFieldIds = FirstSolnOutFieldIdsPrime
    else
        unexpected($pred, "bad FieldIds")
    ),

    ml_gen_string_trie_several_soln_lookup_slots(
        FirstSolnStructType, LaterSolnStructType, CaseIdSolns,
        0, AfterLastCaseNum, 0, cord.init, FirstSolnRowInitializersCord,
        cord.init, LaterSolnRowInitializerCord),
    expect(unify(MaxCaseNum + 1, AfterLastCaseNum), $pred,
        "MaxCaseNum + 1 != AfterLastCaseNum"),
    FirstSolnRowInitializers = cord.list(FirstSolnRowInitializersCord),
    LaterSolnRowInitializers = cord.list(LaterSolnRowInitializerCord),
    ml_gen_static_vector_defn(MLDS_ModuleName, FirstSolnStructTypeNum,
        FirstSolnRowInitializers, FirstSolnVectorCommon,
        GlobalData2, GlobalData3),
    ml_gen_static_vector_defn(MLDS_ModuleName, LaterSolnStructTypeNum,
        LaterSolnRowInitializers, LaterSolnVectorCommon,
        GlobalData3, GlobalData),
    ml_gen_info_set_global_data(GlobalData, !Info),

    ml_gen_several_soln_lookup_code(Context, ml_lval(CaseNumVarLval),
        OutVars, OutTypes, FirstSolnStructType, LaterSolnStructType,
        NumLaterSolnsFieldId, FirstLaterSolnRowFieldId,
        FirstSolnOutFieldIds, LaterSolnOutFieldIds,
        FirstSolnVectorCommon, LaterSolnVectorCommon,
        do_not_need_bit_vec_check_no_gaps,
        MatchDefns, SuccessStmts, !Info),
    SuccessBlockStmt = ml_stmt_block(MatchDefns, [], SuccessStmts, Context),
    (
        CanFail = can_fail,
        IsCaseNumNonNegCond = ml_binop(int_cmp(int_type_int, ge),
            ml_lval(CaseNumVarLval), ml_const(mlconst_int(0))),
        ResultStmt = ml_stmt_if_then_else(IsCaseNumNonNegCond,
            SuccessBlockStmt, no, Context)
    ;
        CanFail = cannot_fail,
        ResultStmt = SuccessBlockStmt
    ),
    Stmt = ml_stmt_block([CaseNumVarDefn], [],
        [InitCaseNumVarStmt, GetCaseNumSwitchStmt, ResultStmt], Context),
    Stmts = [Stmt].

:- pred ml_gen_string_trie_several_soln_lookup_slots(
    mlds_type::in, mlds_type::in,
    assoc_list(case_id, soln_consts(mlds_rval))::in, int::in, int::out,
    int::in,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out) is det.

ml_gen_string_trie_several_soln_lookup_slots(
        _FirstSolnStructType, _LaterSolnStructType, [], !CurCaseNum,
        _CurLaterSolnIndex,
        !FirstSolnRowInitializersCord, !LaterSolnRowInitializersCord).
ml_gen_string_trie_several_soln_lookup_slots(
        FirstSolnStructType, LaterSolnStructType,
        [CaseIdSolns | CaseIdsSolns], !CurCaseNum, !.CurLaterSolnIndex,
        !FirstSolnRowInitializersCord, !LaterSolnRowInitializersCord) :-
    CaseIdSolns = CaseId - Solns,
    CaseId = case_id(CaseIdNum),
    expect(unify(CaseIdNum, !.CurCaseNum), $pred, "CaseIdNum != !.CurCaseNum"),
    (
        Solns = one_soln(FirstSolnRvals),
        NumLaterSolnsRval = ml_const(mlconst_int(0)),
        FirstLaterSlotRval = ml_const(mlconst_int(-1))
    ;
        Solns = several_solns(FirstSolnRvals, LaterSolns),
        list.length(LaterSolns, NumLaterSolns),
        NumLaterSolnsRval = ml_const(mlconst_int(NumLaterSolns)),
        FirstLaterSlotRval = ml_const(mlconst_int(!.CurLaterSolnIndex)),
        LaterSolnRowInitializers = list.map(
            ml_construct_later_soln_row(LaterSolnStructType),
            LaterSolns),
        !:LaterSolnRowInitializersCord = !.LaterSolnRowInitializersCord ++
            cord.from_list(LaterSolnRowInitializers),
        !:CurLaterSolnIndex = !.CurLaterSolnIndex + NumLaterSolns
    ),
    FirstSolnRowRvals =
        [NumLaterSolnsRval, FirstLaterSlotRval | FirstSolnRvals],
    FirstSolnRowInitializer = init_struct(FirstSolnStructType,
        list.map(wrap_init_obj, FirstSolnRowRvals)),
    cord.snoc(FirstSolnRowInitializer, !FirstSolnRowInitializersCord),
    !:CurCaseNum = !.CurCaseNum + 1,
    ml_gen_string_trie_several_soln_lookup_slots(
        FirstSolnStructType, LaterSolnStructType,
        CaseIdsSolns, !CurCaseNum, !.CurLaterSolnIndex,
        !FirstSolnRowInitializersCord, !LaterSolnRowInitializersCord).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% Code useful for both the jump and lookup versions of tries.
%

:- pred ml_create_nested_switch_trie(list(tagged_case)::in, prog_context::in,
    mlds_rval::in, int::out, mlds_lval::out, mlds_local_var_defn::out,
    mlds_stmt::out, mlds_stmt::out, ml_gen_info::in, ml_gen_info::out) is det.

ml_create_nested_switch_trie(TaggedCases, Context, VarRval, MaxCaseNum,
        CaseNumVarLval, CaseNumVarDefn,
        InitCaseNumVarStmt, GetCaseNumSwitchStmt, !Info) :-
    ml_gen_info_get_target(!.Info, Target),
    ( Target = ml_target_c,         CompilationTarget = target_c
    ; Target = ml_target_java,      CompilationTarget = target_java
    ; Target = ml_target_csharp,    CompilationTarget = target_csharp
    ),
    Encoding = target_string_encoding(CompilationTarget),
    build_str_case_id_list(TaggedCases, MaxCaseNum, StrsCaseIds),
    create_trie(Encoding, StrsCaseIds, TopTrieNode),
    ml_gen_trie_case_num_var_and_init(Context, CaseNumVarLval,
        CaseNumVarDefn, InitCaseNumVarStmt, !Info),
    ml_convert_trie_to_nested_switches(Encoding, VarRval, CaseNumVarLval,
        Context, 0, TopTrieNode, GetCaseNumSwitchStmt).

    % Generate the following local variable declaration:
    %   int         case_num = -1;
    %
:- pred ml_gen_trie_case_num_var_and_init(prog_context::in,
    mlds_lval::out, mlds_local_var_defn::out, mlds_stmt::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_trie_case_num_var_and_init(Context, CaseNumVarLval, CaseNumVarDefn,
        InitStmt, !Info) :-
    ml_gen_info_new_aux_var_name(mcav_case_num, CaseNumVarName, !Info),
    CaseNumVarType = mlds_builtin_type_int(int_type_int),
    % We never need to trace ints.
    CaseNumVarDefn = ml_gen_mlds_var_decl(CaseNumVarName, CaseNumVarType,
        gc_no_stmt, Context),
    CaseNumVarLval = ml_local_var(CaseNumVarName, CaseNumVarType),

    InitAssign = assign(CaseNumVarLval, ml_const(mlconst_int(-1))),
    InitStmt = ml_stmt_atomic(InitAssign, Context).

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

:- pred ml_convert_trie_to_nested_switches(string_encoding::in, mlds_rval::in,
    mlds_lval::in, prog_context::in, int::in, trie_node::in, mlds_stmt::out)
    is det.

ml_convert_trie_to_nested_switches(Encoding, VarRval, CaseNumVarLval, Context,
        NumMatched, TrieNode, Stmt) :-
    (
        TrieNode = trie_leaf(RevMatchedCodeUnits, NotYetMatchedCodeUnits,
            CaseId),
        list.reverse(RevMatchedCodeUnits, MatchedCodeUnits),
        AllCodeUnits = MatchedCodeUnits ++ NotYetMatchedCodeUnits,
        list.length(MatchedCodeUnits, NumMatchedCodeUnits),
        expect(unify(NumMatchedCodeUnits, NumMatched), $pred,
            "NumMatchedCodeUnits != NumMatched"),
        det_from_code_unit_list_in_encoding_allow_ill_formed(Encoding,
            AllCodeUnits, EndStr),
        CondRval = ml_binop(offset_str_eq(NumMatched, no_size),
            VarRval, ml_const(mlconst_string(EndStr))),

        CaseId = case_id(CaseNum),
        CaseNumRval = ml_const(mlconst_int(CaseNum)),
        SetCaseNumVarAssign = assign(CaseNumVarLval, CaseNumRval),
        SetCaseNumVarStmt = ml_stmt_atomic(SetCaseNumVarAssign, Context),

        Stmt = ml_stmt_if_then_else(CondRval, SetCaseNumVarStmt, no, Context)
    ;
        TrieNode = trie_choice(RevMatchedCodeUnits, _ChoiceMap, MaybeEnd),
        list.length(RevMatchedCodeUnits, NumRevMatchedCodeUnits),
        expect(unify(NumRevMatchedCodeUnits, NumMatched), $pred,
            "NumRevMatchedCodeUnits != NumMatched"),

        chase_any_stick_in_trie(TrieNode, ChoicePairs, 
            StickCodeUnits, TrieNodeAfterStick),
        (
            StickCodeUnits = [_, _ | _],
            list.length(StickCodeUnits, NumStickCodeUnits),
            CmpOp = offset_str_eq(NumMatched, size(NumStickCodeUnits)),
            list.reverse(RevMatchedCodeUnits, MatchedCodeUnits),
            det_from_code_unit_list_in_encoding_allow_ill_formed(Encoding,
                MatchedCodeUnits ++ StickCodeUnits, MatchedStickStr),
            CondRval = ml_binop(CmpOp,
                VarRval, ml_const(mlconst_string(MatchedStickStr))),
            ml_convert_trie_to_nested_switches(Encoding, VarRval,
                CaseNumVarLval, Context, NumMatched + NumStickCodeUnits,
                TrieNodeAfterStick, AfterStickStmt),
            Stmt = ml_stmt_if_then_else(CondRval, AfterStickStmt, no, Context)
        ;
            ( StickCodeUnits = []
            ; StickCodeUnits = [_]
            ),
            ml_convert_trie_choices_to_nested_switches(Encoding, VarRval,
                CaseNumVarLval, Context, NumMatched + 1, ChoicePairs,
                cord.init, SwitchArmsCord0),
            SwitchArms0 = cord.list(SwitchArmsCord0),
            (
                MaybeEnd = no,
                SwitchArms = SwitchArms0
            ;
                MaybeEnd = yes(EndCaseId),

                NullCodeUnit = 0,    % Match the terminating NUL character.
                NullMatchCond =
                    match_value(ml_const(mlconst_int(NullCodeUnit))),

                EndCaseId = case_id(EndCaseNum),
                EndCaseNumRval = ml_const(mlconst_int(EndCaseNum)),
                EndSetCaseNumVarAssign =
                    assign(CaseNumVarLval, EndCaseNumRval),
                EndSetCaseNumVarStmt =
                    ml_stmt_atomic(EndSetCaseNumVarAssign, Context),
                EndSwitchArm = mlds_switch_case(NullMatchCond, [],
                    EndSetCaseNumVarStmt),
                SwitchArms = [EndSwitchArm | SwitchArms0]
            ),
            SwitchCodeUnitRval = ml_binop(string_unsafe_index_code_unit,
                VarRval, ml_const(mlconst_int(NumMatched))),
            % Could we set this to a known range? If we could,
            % would it be useful?
            SwitchRange = mlds_switch_range_unknown,
            Stmt = ml_stmt_switch( mlds_builtin_type_int(int_type_int),
                SwitchCodeUnitRval, SwitchRange, SwitchArms,
                default_do_nothing, Context)
        )
    ).

:- pred ml_convert_trie_choices_to_nested_switches(string_encoding::in,
    mlds_rval::in, mlds_lval::in, prog_context::in, int::in,
    assoc_list(int, trie_node)::in,
    cord(mlds_switch_case)::in, cord(mlds_switch_case)::out) is det.

ml_convert_trie_choices_to_nested_switches(_, _, _, _, _, [], !SwitchArmsCord).
ml_convert_trie_choices_to_nested_switches(Encoding, VarRval, CaseNumVarLval,
        Context, NumMatched, [ChoicePair | ChoicePairs], !SwitchArmsCord) :-
    ChoicePair = CodeUnit - SubTrieNode,
    ml_convert_trie_to_nested_switches(Encoding, VarRval, CaseNumVarLval,
        Context, NumMatched, SubTrieNode, SwitchArmStmt),
    MatchCond = match_value(ml_const(mlconst_int(CodeUnit))),
    SwitchArm = mlds_switch_case(MatchCond, [], SwitchArmStmt),
    cord.snoc(SwitchArm, !SwitchArmsCord),
    ml_convert_trie_choices_to_nested_switches(Encoding, VarRval,
        CaseNumVarLval, Context, NumMatched, ChoicePairs, !SwitchArmsCord).

:- pred ml_generate_arms_of_switch_on_case_id(
    assoc_list(case_id, mlds_stmt)::in,
    cord(mlds_switch_case)::in, cord(mlds_switch_case)::out) is det.

ml_generate_arms_of_switch_on_case_id([], !CaseCord).
ml_generate_arms_of_switch_on_case_id([CasePair | CasePairs], !CaseCord) :-
    CasePair = CaseId - CaseStmt,
    CaseId = case_id(CaseNum),
    MatchCond = match_value(ml_const(mlconst_int(CaseNum))),
    Case = mlds_switch_case(MatchCond, [], CaseStmt),
    cord.snoc(Case, !CaseCord),
    ml_generate_arms_of_switch_on_case_id(CasePairs, !CaseCord).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of jump hash tables.
%

ml_generate_string_hash_jump_switch(VarRval, TaggedCases, CodeModel, CanFail,
        EntryPackedArgsMap, Context, Defns, Stmts, !Info) :-
    ml_gen_tagged_case_codes_for_string_switch(CodeModel, EntryPackedArgsMap,
        TaggedCases, map.init, CodeMap, [], ReachableConstVarMaps, !Info),
    ml_gen_record_consensus_const_var_map(ReachableConstVarMaps, !Info),
    build_str_case_id_list(TaggedCases, _MaxCaseNum, StrsCaseIds),

    % Compute the hash table.
    construct_string_hash_cases(StrsCaseIds, allow_doubling,
        TableSize, HashSlotMap, HashOp, NumCollisions),
    HashMask = TableSize - 1,

    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    ml_gen_info_get_target(!.Info, Target),

    MLDS_StringType = mlds_builtin_type_string,
    MLDS_IntType = mlds_builtin_type_int(int_type_int),

    ( if NumCollisions = 0 then
        MLDS_ArgTypes = [MLDS_StringType],
        LoopPresent = no
    else
        MLDS_ArgTypes = [MLDS_StringType, MLDS_IntType],
        LoopPresent = yes
    ),

    ml_gen_string_hash_switch_search_vars(CodeModel, CanFail, LoopPresent,
        Context, VarRval, HashSearchInfo, !Info),
    HashSearchInfo = ml_hash_search_info(_CodeModel, _LoopPresent,
        _Context, _VarRval, SlotVarNameType, _StringVarNameType,
        _MaybeStopLoopNameType, _FailStmts, Defns),

    SlotVarNameType = mlds_local_var_name_type(SlotVarName, SlotVarType),
    SlotVarLval = ml_local_var(SlotVarName, SlotVarType),

    ml_gen_info_get_global_data(!.Info, GlobalData0),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        MLDS_ArgTypes, StructTypeNum, StructType, FieldIds,
        GlobalData0, GlobalData1),
    ( if NumCollisions = 0 then
        (
            FieldIds = [StringFieldId],
            MaybeNextSlotFieldId = no
        ;
            ( FieldIds = []
            ; FieldIds = [_, _ | _]
            ),
            unexpected($pred, "bad FieldIds")
        )
    else
        (
            FieldIds = [StringFieldId, NextSlotFieldId],
            MaybeNextSlotFieldId = yes(NextSlotFieldId)
        ;
            ( FieldIds = []
            ; FieldIds = [_]
            ; FieldIds = [_, _, _ | _]
            ),
            unexpected($pred, "bad FieldIds")
        )
    ),
    % Generate the rows of the hash table.
    ml_gen_string_hash_jump_slots(0, TableSize, HashSlotMap, StructType,
        MaybeNextSlotFieldId, cord.init, RowInitializersCord,
        map.init, RevMap),
    RowInitializers = cord.list(RowInitializersCord),
    % Generate the hash table.
    ml_gen_static_vector_defn(MLDS_ModuleName, StructTypeNum, RowInitializers,
        VectorCommon, GlobalData1, GlobalData),
    ml_gen_info_set_global_data(GlobalData, !Info),

    % Generate code which does the hash table lookup.
    map.to_assoc_list(RevMap, RevMapEntries),
    generate_string_jump_switch_arms(CodeMap, RevMapEntries, [], SlotsCases0),
    list.sort(SlotsCases0, SlotsCases),

    SwitchStmt0 = ml_stmt_switch(SlotVarType, ml_lval(SlotVarLval),
        mlds_switch_range(0, TableSize - 1), SlotsCases,
        default_is_unreachable, Context),
    ml_simplify_switch(SwitchStmt0, SwitchStmt, !Info),

    FoundMatchComment = "we found a match; dispatch to the corresponding code",
    FoundMatchStmts = [
        ml_stmt_atomic(comment(FoundMatchComment), Context),
        SwitchStmt
    ],

    InitialComment = "hashed string jump switch",
    ml_gen_string_hash_switch_search(InitialComment, HashSearchInfo,
        HashOp, VectorCommon, StructType,
        StringFieldId, MaybeNextSlotFieldId, HashMask,
        [], FoundMatchStmts, Stmts, !Info).

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

:- pred ml_gen_tagged_case_codes_for_string_switch(code_model::in,
    packed_word_map::in, list(tagged_case)::in,
    map(case_id, mlds_stmt)::in, map(case_id, mlds_stmt)::out,
    list(ml_ground_term_map)::in, list(ml_ground_term_map)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_tagged_case_codes_for_string_switch(_CodeModel, _EntryPackedArgsMap, [],
        !CodeMap, !ReachableConstVarMaps, !Info).
ml_gen_tagged_case_codes_for_string_switch(CodeModel, EntryPackedArgsMap,
        [TaggedCase | TaggedCases], !CodeMap, !ReachableConstVarMaps, !Info) :-
    ml_gen_tagged_case_code_for_string_switch(CodeModel, EntryPackedArgsMap,
        TaggedCase, !CodeMap, !ReachableConstVarMaps, !Info),
    ml_gen_tagged_case_codes_for_string_switch(CodeModel, EntryPackedArgsMap,
        TaggedCases, !CodeMap, !ReachableConstVarMaps, !Info).

:- pred ml_gen_tagged_case_code_for_string_switch_return_case_id(
    code_model::in, packed_word_map::in, tagged_case::in, case_id::out,
    map(case_id, mlds_stmt)::in, map(case_id, mlds_stmt)::out,
    list(ml_ground_term_map)::in, list(ml_ground_term_map)::out,
    ml_gen_info::in, ml_gen_info::out, unit::in, unit::out) is det.

ml_gen_tagged_case_code_for_string_switch_return_case_id(CodeModel,
        EntryPackedArgsMap, TaggedCase, CaseId,
        !CodeMap, !ReachableConstVarMaps, !Info, _, unit) :-
    TaggedCase = tagged_case(_, _, CaseId, _),
    ml_gen_tagged_case_code_for_string_switch(CodeModel, EntryPackedArgsMap,
        TaggedCase, !CodeMap, !ReachableConstVarMaps, !Info).

:- pred ml_gen_tagged_case_code_for_string_switch(code_model::in,
    packed_word_map::in, tagged_case::in,
    map(case_id, mlds_stmt)::in, map(case_id, mlds_stmt)::out,
    list(ml_ground_term_map)::in, list(ml_ground_term_map)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_tagged_case_code_for_string_switch(CodeModel, EntryPackedArgsMap,
        TaggedCase, !CodeMap, !ReachableConstVarMaps, !Info) :-
    ml_gen_info_set_packed_word_map(EntryPackedArgsMap, !Info),
    TaggedCase = tagged_case(MainTaggedConsId, OtherTaggedConsIds,
        CaseId, Goal),
    ml_gen_goal_as_branch_block(CodeModel, Goal, GoalStmt,
        !ReachableConstVarMaps, !Info),
    MainString = gen_string_switch_case_comment(MainTaggedConsId),
    OtherStrings = list.map(gen_string_switch_case_comment,
        OtherTaggedConsIds),
    Strings = string.join_list(", ", [MainString | OtherStrings]),
    % Note that the order of the strings in the comment will in general
    % not match the order of the hash slots for which the case applies.
    % In other words, if e.g. OtherTaggedConsIds has two elements and
    % CaseStmt has the C code "case Slot1: case Slot2: case Slot3:"
    % generated in front of it, Slot1 can be the slot of any of
    % MainTaggedConsId and the two OtherTaggedConsIds; it will be the slot
    % of MainTaggedConsId only by accident.
    CommentString = "case " ++ Strings,
    Goal = hlds_goal(_GoalExpr, GoalInfo),
    Context = goal_info_get_context(GoalInfo),
    Comment = ml_stmt_atomic(comment(CommentString), Context),
    CaseStmt = ml_stmt_block([], [], [Comment, GoalStmt], Context),
    map.det_insert(CaseId, CaseStmt, !CodeMap).

:- func gen_string_switch_case_comment(tagged_cons_id) = string.

gen_string_switch_case_comment(TaggedConsId) = String :-
    TaggedConsId = tagged_cons_id(_ConsId, ConsTag),
    ( if ConsTag = string_tag(ConsString) then
        String = """" ++ ConsString ++ """"
    else
        unexpected($pred, "non-string tag")
    ).

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

    % A list of all the hash slots that all have the same code.
    %
:- type hash_slots
    --->    hash_slots(int, cord(int)).

    % Maps case numbers (each of which identifies the code of one switch arm)
    % to the hash slots that share that code.
    %
:- type hash_slot_rev_map == map(case_id, hash_slots).

:- pred ml_gen_string_hash_jump_slots(int::in, int::in,
    map(int, string_hash_slot(case_id))::in,
    mlds_type::in, maybe(mlds_field_id)::in,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out,
    hash_slot_rev_map::in, hash_slot_rev_map::out) is det.

ml_gen_string_hash_jump_slots(Slot, TableSize, HashSlotMap, StructType,
        MaybeNextSlotId, !RowInitializersCord, !RevMap) :-
    ( if Slot = TableSize then
        true
    else
        ml_gen_string_hash_jump_slot(Slot, HashSlotMap,
            StructType, MaybeNextSlotId, RowInitializer, !RevMap),
        cord.snoc(RowInitializer, !RowInitializersCord),
        ml_gen_string_hash_jump_slots(Slot + 1, TableSize, HashSlotMap,
            StructType, MaybeNextSlotId, !RowInitializersCord, !RevMap)
    ).

:- pred ml_gen_string_hash_jump_slot(int::in,
    map(int, string_hash_slot(case_id))::in,
    mlds_type::in, maybe(mlds_field_id)::in, mlds_initializer::out,
    hash_slot_rev_map::in, hash_slot_rev_map::out) is det.

ml_gen_string_hash_jump_slot(Slot, HashSlotMap, StructType,
        MaybeNextSlotId, RowInitializer, !RevMap) :-
    ( if map.search(HashSlotMap, Slot, HashSlotMapEntry) then
        HashSlotMapEntry = string_hash_slot(String, Next, CaseId),
        StringRval = ml_const(mlconst_string(String)),
        NextSlotRval = ml_const(mlconst_int(Next)),
        ( if map.search(!.RevMap, CaseId, OldEntry) then
            OldEntry = hash_slots(OldFirstSlot, LaterSlotsCord0),
            cord.snoc(Slot, LaterSlotsCord0, LaterSlotsCord),
            NewEntry = hash_slots(OldFirstSlot, LaterSlotsCord),
            map.det_update(CaseId, NewEntry, !RevMap)
        else
            NewEntry = hash_slots(Slot, cord.init),
            map.det_insert(CaseId, NewEntry, !RevMap)
        )
    else
        StringRval = ml_const(mlconst_null(mlds_builtin_type_string)),
        NextSlotRval = ml_const(mlconst_int(-2))
    ),
    (
        MaybeNextSlotId = yes(_),
        RowInitializer = init_struct(StructType,
            [init_obj(StringRval), init_obj(NextSlotRval)])
    ;
        MaybeNextSlotId = no,
        RowInitializer = init_struct(StructType,
            [init_obj(StringRval)])
    ).

:- pred generate_string_jump_switch_arms(map(case_id, mlds_stmt)::in,
    assoc_list(case_id, hash_slots)::in,
    list(mlds_switch_case)::in, list(mlds_switch_case)::out) is det.

generate_string_jump_switch_arms(_, [], !Cases).
generate_string_jump_switch_arms(CodeMap, [Entry | Entries], !Cases) :-
    Entry = CaseId - HashSlots,
    HashSlots = hash_slots(FirstHashSlot, LaterHashSlotsCord),
    LaterHashSlots = cord.list(LaterHashSlotsCord),
    FirstMatchCond = make_hash_match(FirstHashSlot),
    LaterMatchConds = list.map(make_hash_match, LaterHashSlots),
    map.lookup(CodeMap, CaseId, CaseStmt),
    Case = mlds_switch_case(FirstMatchCond, LaterMatchConds, CaseStmt),
    !:Cases = [Case | !.Cases],
    generate_string_jump_switch_arms(CodeMap, Entries, !Cases).

:- func make_hash_match(int) = mlds_case_match_cond.

make_hash_match(Slot) = match_value(ml_const(mlconst_int(Slot))).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of lookup hash tables.
%

ml_generate_string_hash_lookup_switch(VarRval, TaggedCases, LookupSwitchInfo,
        CodeModel, CanFail, Context, Defns, Stmts, !:Info) :-
    LookupSwitchInfo =
        ml_lookup_switch_info(CaseIdConsts, OutVars, OutTypes, !:Info),
    (
        CaseIdConsts = all_one_soln(CaseIdValueMap),
        ml_case_id_soln_consts_to_tag_soln_consts(get_string_tag, TaggedCases,
            CaseIdValueMap, StrValueMap),
        map.to_assoc_list(StrValueMap, StrValues),
        ml_generate_string_hash_simple_lookup_switch(VarRval,
            StrValues, CodeModel, CanFail, OutVars, OutTypes, Context,
            Defns, Stmts, !Info)
    ;
        CaseIdConsts = some_several_solns(CaseIdSolnMap, _Unit),
        expect(unify(CodeModel, model_non), $pred, "CodeModel != model_non"),
        ml_case_id_soln_consts_to_tag_soln_consts(get_string_tag, TaggedCases,
            CaseIdSolnMap, StrSolnMap),
        map.to_assoc_list(StrSolnMap, StrSolns),
        ml_generate_string_hash_several_soln_lookup_switch(VarRval,
            StrSolns, CodeModel, CanFail, OutVars, OutTypes, Context,
            Defns, Stmts, !Info)
    ).

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

:- pred ml_generate_string_hash_simple_lookup_switch(mlds_rval::in,
    assoc_list(string, list(mlds_rval))::in, code_model::in, can_fail::in,
    list(prog_var)::in, list(mlds_type)::in, prog_context::in,
    list(mlds_local_var_defn)::out, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_generate_string_hash_simple_lookup_switch(VarRval, CaseValues,
        CodeModel, CanFail, OutVars, OutTypes, Context, Defns, Stmts, !Info) :-
    % Compute the hash table.
    construct_string_hash_cases(CaseValues, allow_doubling,
        TableSize, HashSlotMap, HashOp, NumCollisions),
    HashMask = TableSize - 1,

    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    ml_gen_info_get_target(!.Info, Target),

    MLDS_StringType = mlds_builtin_type_string,
    MLDS_IntType = mlds_builtin_type_int(int_type_int),

    ( if NumCollisions = 0 then
        MLDS_ArgTypes = [MLDS_StringType | OutTypes],
        LoopPresent = no
    else
        MLDS_ArgTypes = [MLDS_StringType, MLDS_IntType | OutTypes],
        LoopPresent = yes
    ),

    ml_gen_string_hash_switch_search_vars(CodeModel, CanFail, LoopPresent,
        Context, VarRval, HashSearchInfo, !Info),
    HashSearchInfo = ml_hash_search_info(_CodeModel, _LoopPresent,
        _Context, _VarRval, SlotVarNameType, _StringVarNameType,
        _MaybeStopLoopVarNameType, _FailStmts, Defns),

    SlotVarNameType = mlds_local_var_name_type(SlotVarName, SlotVarType),
    SlotVarLval = ml_local_var(SlotVarName, SlotVarType),
    SlotVarRval = ml_lval(SlotVarLval),

    ml_gen_info_get_global_data(!.Info, GlobalData0),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        MLDS_ArgTypes, StructTypeNum, StructType, FieldIds,
        GlobalData0, GlobalData1),
    ( if NumCollisions = 0 then
        (
            FieldIds = [StringFieldId | OutFieldIds],
            MaybeNextSlotFieldId = no
        ;
            FieldIds = [],
            unexpected($pred, "bad FieldIds")
        )
    else
        (
            FieldIds = [StringFieldId, NextSlotFieldId | OutFieldIds],
            MaybeNextSlotFieldId = yes(NextSlotFieldId)
        ;
            ( FieldIds = []
            ; FieldIds = [_]
            ),
            unexpected($pred, "bad FieldIds")
        )
    ),
    % Generate the rows of the hash table.
    DummyOutRvals = list.map(ml_default_value_for_type, OutTypes),
    DummyOutInitializers = list.map(wrap_init_obj, DummyOutRvals),
    ml_gen_string_hash_simple_lookup_slots(0, TableSize, StructType,
        HashSlotMap, MaybeNextSlotFieldId, DummyOutInitializers,
        cord.init, RowInitializersCord),
    RowInitializers = cord.list(RowInitializersCord),
    % Generate the hash table.
    ml_gen_static_vector_defn(MLDS_ModuleName, StructTypeNum, RowInitializers,
        VectorCommon, GlobalData1, GlobalData),
    ml_gen_info_set_global_data(GlobalData, !Info),

    ml_generate_field_assigns(OutVars, OutTypes, OutFieldIds, VectorCommon,
        StructType, SlotVarRval, Context, LookupStmts, !Info),

    FoundMatchComment = "we found a match; look up the results",
    FoundMatchCommentStmt =
        ml_stmt_atomic(comment(FoundMatchComment), Context),

    (
        CodeModel = model_det,
        MatchStmts = [FoundMatchCommentStmt | LookupStmts]
    ;
        CodeModel = model_semi,
        ml_gen_set_success(ml_const(mlconst_true), Context, SetSuccessTrueStmt,
            !Info),
        MatchStmts = [FoundMatchCommentStmt | LookupStmts] ++
            [SetSuccessTrueStmt]
    ;
        CodeModel = model_non,
        unexpected($pred, "model_non")
    ),

    InitialComment = "hashed string simple lookup switch",
    ml_gen_string_hash_switch_search(InitialComment, HashSearchInfo,
        HashOp, VectorCommon, StructType,
        StringFieldId, MaybeNextSlotFieldId, HashMask,
        [], MatchStmts, Stmts, !Info).

:- pred ml_gen_string_hash_simple_lookup_slots(int::in, int::in, mlds_type::in,
    map(int, string_hash_slot(list(mlds_rval)))::in, maybe(mlds_field_id)::in,
    list(mlds_initializer)::in,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out) is det.

ml_gen_string_hash_simple_lookup_slots(Slot, TableSize, StructType,
        HashSlotMap, MaybeNextSlotId, DummyOutInitializers,
        !RowInitializersCord) :-
    ( if Slot = TableSize then
        true
    else
        ml_gen_string_hash_simple_lookup_slot(Slot, StructType, HashSlotMap,
            MaybeNextSlotId, DummyOutInitializers, RowInitializer),
        cord.snoc(RowInitializer, !RowInitializersCord),
        ml_gen_string_hash_simple_lookup_slots(Slot + 1, TableSize,
            StructType, HashSlotMap, MaybeNextSlotId,
            DummyOutInitializers, !RowInitializersCord)
    ).

:- pred ml_gen_string_hash_simple_lookup_slot(int::in, mlds_type::in,
    map(int, string_hash_slot(list(mlds_rval)))::in,
    maybe(mlds_field_id)::in,
    list(mlds_initializer)::in, mlds_initializer::out) is det.

ml_gen_string_hash_simple_lookup_slot(Slot, StructType, HashSlotMap,
        MaybeNextSlotId, DummyOutInitializers, RowInitializer) :-
    ( if map.search(HashSlotMap, Slot, HashSlotMapEntry) then
        HashSlotMapEntry = string_hash_slot(String, Next, OutRvals),
        StringRval = ml_const(mlconst_string(String)),
        NextSlotRval = ml_const(mlconst_int(Next)),
        OutInitializers = list.map(wrap_init_obj, OutRvals)
    else
        StringRval = ml_const(mlconst_null(mlds_builtin_type_string)),
        NextSlotRval = ml_const(mlconst_int(-2)),
        OutInitializers = DummyOutInitializers
    ),
    (
        MaybeNextSlotId = yes(_),
        RowInitializer = init_struct(StructType,
            [init_obj(StringRval), init_obj(NextSlotRval) | OutInitializers])
    ;
        MaybeNextSlotId = no,
        RowInitializer = init_struct(StructType,
            [init_obj(StringRval) | OutInitializers])
    ).

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

:- pred ml_generate_string_hash_several_soln_lookup_switch(mlds_rval::in,
    assoc_list(string, soln_consts(mlds_rval))::in,
    code_model::in, can_fail::in,
    list(prog_var)::in, list(mlds_type)::in, prog_context::in,
    list(mlds_local_var_defn)::out, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_generate_string_hash_several_soln_lookup_switch(VarRval, CaseSolns,
        CodeModel, CanFail, OutVars, OutTypes, Context, Defns, Stmts, !Info) :-
    % Compute the hash table.
    construct_string_hash_cases(CaseSolns, allow_doubling,
        TableSize, HashSlotMap, HashOp, NumCollisions),
    HashMask = TableSize - 1,

    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    ml_gen_info_get_target(!.Info, Target),

    MLDS_StringType = mlds_builtin_type_string,
    MLDS_IntType = mlds_builtin_type_int(int_type_int),

    ( if NumCollisions = 0 then
        FirstSolnFieldTypes = [MLDS_StringType,
            MLDS_IntType, MLDS_IntType | OutTypes],
        LoopPresent = no
    else
        FirstSolnFieldTypes = [MLDS_StringType, MLDS_IntType,
            MLDS_IntType, MLDS_IntType | OutTypes],
        LoopPresent = yes
    ),

    ml_gen_string_hash_switch_search_vars(CodeModel, CanFail, LoopPresent,
        Context, VarRval, HashSearchInfo, !Info),
    HashSearchInfo = ml_hash_search_info(_CodeModel, _LoopPresent,
        _Context, _VarRval, SlotVarNameType, _StringVarNameType,
        _MaybeStopLoopVarNameType, _FailStmts, Defns),

    SlotVarNameType = mlds_local_var_name_type(SlotVarName, SlotVarType),
    SlotVarLval = ml_local_var(SlotVarName, SlotVarType),

    ml_gen_info_get_global_data(!.Info, GlobalData0),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        FirstSolnFieldTypes, FirstSolnStructTypeNum, FirstSolnStructType,
        FirstSolnFieldIds, GlobalData0, GlobalData1),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        OutTypes, LaterSolnStructTypeNum, LaterSolnStructType,
        LaterSolnOutFieldIds, GlobalData1, GlobalData2),
    ( if NumCollisions = 0 then
        ( if
            FirstSolnFieldIds = [StringFieldIdPrime,
                NumLaterSolnsFieldIdPrime, FirstLaterSolnRowFieldIdPrime
                | FirstSolnOutFieldIdsPrime]
        then
            StringFieldId = StringFieldIdPrime,
            NumLaterSolnsFieldId = NumLaterSolnsFieldIdPrime,
            FirstLaterSolnRowFieldId = FirstLaterSolnRowFieldIdPrime,
            FirstSolnOutFieldIds = FirstSolnOutFieldIdsPrime,
            MaybeNextSlotFieldId = no
        else
            unexpected($pred, "bad FieldIds")
        )
    else
        ( if
            FirstSolnFieldIds =
                [StringFieldIdPrime, NextSlotFieldIdPrime,
                NumLaterSolnsFieldIdPrime, FirstLaterSolnRowFieldIdPrime
                | FirstSolnOutFieldIdsPrime]
        then
            StringFieldId = StringFieldIdPrime,
            NumLaterSolnsFieldId = NumLaterSolnsFieldIdPrime,
            FirstLaterSolnRowFieldId = FirstLaterSolnRowFieldIdPrime,
            FirstSolnOutFieldIds = FirstSolnOutFieldIdsPrime,
            MaybeNextSlotFieldId = yes(NextSlotFieldIdPrime)
        else
            unexpected($pred, "bad FieldIds")
        )
    ),

    % Generate the rows of the first soln hash table.
    DummyOutRvals = list.map(ml_default_value_for_type, OutTypes),
    DummyOutInitializers = list.map(wrap_init_obj, DummyOutRvals),
    ml_gen_string_hash_several_soln_lookup_slots(0, TableSize, HashSlotMap,
        FirstSolnStructType, LaterSolnStructType,
        MaybeNextSlotFieldId, DummyOutInitializers,
        cord.init, FirstSolnRowInitializersCord,
        cord.init, LaterSolnRowInitializersCord, 0),
    FirstSolnRowInitializers = cord.list(FirstSolnRowInitializersCord),
    LaterSolnRowInitializers = cord.list(LaterSolnRowInitializersCord),
    % Generate the hash table.
    ml_gen_static_vector_defn(MLDS_ModuleName, FirstSolnStructTypeNum,
        FirstSolnRowInitializers, FirstSolnVectorCommon,
        GlobalData2, GlobalData3),
    ml_gen_static_vector_defn(MLDS_ModuleName, LaterSolnStructTypeNum,
        LaterSolnRowInitializers, LaterSolnVectorCommon,
        GlobalData3, GlobalData),
    ml_gen_info_set_global_data(GlobalData, !Info),

    ml_gen_several_soln_lookup_code(Context,
        ml_lval(SlotVarLval), OutVars, OutTypes,
        FirstSolnStructType, LaterSolnStructType,
        NumLaterSolnsFieldId, FirstLaterSolnRowFieldId,
        FirstSolnOutFieldIds, LaterSolnOutFieldIds,
        FirstSolnVectorCommon, LaterSolnVectorCommon,
        do_not_need_bit_vec_check_no_gaps,
        MatchDefns, SuccessStmts, !Info),

    InitialComment = "hashed string several_soln lookup switch",
    ml_gen_string_hash_switch_search(InitialComment, HashSearchInfo,
        HashOp, FirstSolnVectorCommon, FirstSolnStructType,
        StringFieldId, MaybeNextSlotFieldId, HashMask,
        MatchDefns, SuccessStmts, Stmts, !Info).

:- pred ml_gen_string_hash_several_soln_lookup_slots(int::in, int::in,
    map(int, string_hash_slot(soln_consts(mlds_rval)))::in,
    mlds_type::in, mlds_type::in,
    maybe(mlds_field_id)::in, list(mlds_initializer)::in,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out, int::in) is det.

ml_gen_string_hash_several_soln_lookup_slots(Slot, TableSize,
        HashSlotMap, FirstSolnStructType, LaterSolnStructType,
        MaybeNextSlotId, DummyOutInitializers,
        !FirstSolnRowInitializersCord, !LaterSolnRowInitializersCord,
        !.CurLaterSolnIndex) :-
    ( if Slot = TableSize then
        true
    else
        ml_gen_string_hash_several_soln_lookup_slot(Slot, HashSlotMap,
            FirstSolnStructType, LaterSolnStructType, MaybeNextSlotId,
            DummyOutInitializers, FirstSolnsRowInitializer,
            !LaterSolnRowInitializersCord, !CurLaterSolnIndex),
        cord.snoc(FirstSolnsRowInitializer, !FirstSolnRowInitializersCord),
        ml_gen_string_hash_several_soln_lookup_slots(Slot + 1, TableSize,
            HashSlotMap, FirstSolnStructType, LaterSolnStructType,
            MaybeNextSlotId,
            DummyOutInitializers, !FirstSolnRowInitializersCord,
            !LaterSolnRowInitializersCord, !.CurLaterSolnIndex)
    ).

:- pred ml_gen_string_hash_several_soln_lookup_slot(int::in,
    map(int, string_hash_slot(soln_consts(mlds_rval)))::in,
    mlds_type::in, mlds_type::in, maybe(mlds_field_id)::in,
    list(mlds_initializer)::in, mlds_initializer::out,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out,
    int::in, int::out) is det.

ml_gen_string_hash_several_soln_lookup_slot(Slot, HashSlotMap,
        FirstSolnStructType, LaterSolnStructType, MaybeNextSlotId,
        DummyOutInitializers, FirstSolnsRowInitializer,
        !LaterSolnRowInitializersCord, !CurLaterSolnIndex) :-
    ( if map.search(HashSlotMap, Slot, HashSlotMapEntry) then
        HashSlotMapEntry = string_hash_slot(String, Next, Solns),
        StringRval = ml_const(mlconst_string(String)),
        NextSlotRval = ml_const(mlconst_int(Next)),
        (
            Solns = one_soln(FirstSolnRvals),
            NumLaterSolnsRval = ml_const(mlconst_int(0)),
            FirstLaterSlotRval = ml_const(mlconst_int(-1)),
            FirstSolnOutInitializers = list.map(wrap_init_obj, FirstSolnRvals)
        ;
            Solns = several_solns(FirstSolnRvals, LaterSolns),
            list.length(LaterSolns, NumLaterSolns),
            NumLaterSolnsRval = ml_const(mlconst_int(NumLaterSolns)),
            FirstLaterSlotRval = ml_const(mlconst_int(!.CurLaterSolnIndex)),
            FirstSolnOutInitializers = list.map(wrap_init_obj, FirstSolnRvals),
            LaterSolnRowInitializers = list.map(
                ml_construct_later_soln_row(LaterSolnStructType),
                LaterSolns),
            !:LaterSolnRowInitializersCord = !.LaterSolnRowInitializersCord ++
                from_list(LaterSolnRowInitializers),
            !:CurLaterSolnIndex = !.CurLaterSolnIndex + NumLaterSolns
        )
    else
        StringRval = ml_const(mlconst_null(mlds_builtin_type_string)),
        NextSlotRval = ml_const(mlconst_int(-2)),
        NumLaterSolnsRval = ml_const(mlconst_int(-1)),
        FirstLaterSlotRval = ml_const(mlconst_int(-1)),
        FirstSolnOutInitializers = DummyOutInitializers
    ),
    (
        MaybeNextSlotId = yes(_),
        FirstSolnsRowInitializer = init_struct(FirstSolnStructType,
            [init_obj(StringRval), init_obj(NextSlotRval),
            init_obj(NumLaterSolnsRval), init_obj(FirstLaterSlotRval)
            | FirstSolnOutInitializers])
    ;
        MaybeNextSlotId = no,
        FirstSolnsRowInitializer = init_struct(FirstSolnStructType,
            [init_obj(StringRval),
            init_obj(NumLaterSolnsRval), init_obj(FirstLaterSlotRval)
            | FirstSolnOutInitializers])
    ).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% Code useful for both jump and lookup hash string switches.
%

:- type ml_hash_search_info
    --->    ml_hash_search_info(
                mhsi_code_model         :: code_model,
                mhsi_loop_present       :: bool,
                mhsi_context            :: prog_context,
                mhsi_switch_var         :: mlds_rval,
                mhsi_slot_var           :: mlds_local_var_name_type,
                mhsi_string_var         :: mlds_local_var_name_type,
                mhsi_stop_loop_var      :: maybe(mlds_local_var_name_type),
                mhsi_fail_statements    :: list(mlds_stmt),
                mhsi_defns              :: list(mlds_local_var_defn)
            ).

:- pred ml_gen_string_hash_switch_search_vars(code_model::in, can_fail::in,
    bool::in, prog_context::in, mlds_rval::in,
    ml_hash_search_info::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_string_hash_switch_search_vars(CodeModel, CanFail, LoopPresent,
        Context, VarRval, HashSearchInfo, !Info) :-
    % Generate the following local variable declarations:
    %   int         slot;
    %   MR_String   str;

    ml_gen_info_new_aux_var_name(mcav_slot, SlotVarName, !Info),
    SlotVarType = mlds_builtin_type_int(int_type_int),
    % We never need to trace ints.
    SlotVarDefn = ml_gen_mlds_var_decl(SlotVarName, SlotVarType,
        gc_no_stmt, Context),
    SlotVarNameType = mlds_local_var_name_type(SlotVarName, SlotVarType),

    ml_gen_info_new_aux_var_name(mcav_str, StringVarName, !Info),
    StringVarType = mlds_builtin_type_string,
    % StringVar always points to an element of the string_table array.
    % All those elements are static constants; they can never point into
    % the heap. So GC never needs to trace StringVar.
    StringVarDefn = ml_gen_mlds_var_decl(StringVarName, StringVarType,
        gc_no_stmt, Context),
    StringVarNameType = mlds_local_var_name_type(StringVarName, StringVarType),

    AlwaysDefns = [SlotVarDefn, StringVarDefn],
    ml_should_use_stop_loop(Context, LoopPresent,
        MaybeStopLoopVarNameType, StopLoopVarDefns, !Info),
    Defns = AlwaysDefns ++ StopLoopVarDefns,

    % Generate the code for when the lookup fails.
    ml_gen_maybe_switch_failure(CodeModel, CanFail, Context, FailStmts, !Info),

    HashSearchInfo = ml_hash_search_info(CodeModel, LoopPresent, Context,
        VarRval, SlotVarNameType, StringVarNameType, MaybeStopLoopVarNameType,
        FailStmts, Defns).

:- pred ml_gen_string_hash_switch_search(string::in, ml_hash_search_info::in,
    unary_op::in, mlds_vector_common::in, mlds_type::in,
    mlds_field_id::in, maybe(mlds_field_id)::in, int::in,
    list(mlds_local_var_defn)::in, list(mlds_stmt)::in, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_string_hash_switch_search(InitialComment,
        HashSearchInfo, HashOp, VectorCommon, StructType,
        StringFieldId, MaybeNextSlotFieldId, HashMask,
        MatchDefns, MatchStmts, Stmts, !Info) :-
    HashSearchInfo = ml_hash_search_info(CodeModel, LoopPresent,
        Context, VarRval, SlotVarNameType, StringVarNameType,
        MaybeStopLoopVarNameType, FailStmts, _Defns),

    SlotVarNameType = mlds_local_var_name_type(SlotVarName, SlotVarType),
    SlotVarLval = ml_local_var(SlotVarName, SlotVarType),
    SlotVarRval = ml_lval(SlotVarLval),

    StringVarNameType = mlds_local_var_name_type(StringVarName, StringVarType),
    StringVarLval = ml_local_var(StringVarName, StringVarType),
    StringVarRval = ml_lval(StringVarLval),

    (
        MaybeStopLoopVarNameType = no,
        MaybeStopLoopVarLval = no,
        StopLoopVarNameList = []
    ;
        MaybeStopLoopVarNameType = yes(StopLoopVarNameType),
        StopLoopVarNameType =
            mlds_local_var_name_type(StopLoopVarName, StopLoopVarType),
        StopLoopVarLval0 = ml_local_var(StopLoopVarName, StopLoopVarType),
        MaybeStopLoopVarLval = yes(StopLoopVarLval0),
        StopLoopVarNameList = [StopLoopVarName]
    ),

    ml_wrap_loop_break(CodeModel, LoopPresent, Context, MaybeStopLoopVarLval,
        MatchDefns, MatchStmts, FailStmts,
        SetupForFailStmts, SuccessStmt, AfterStmts, !Info),

    InitialCommentStmt = ml_stmt_atomic(comment(InitialComment), Context),
    PrepareForMatchStmts = [
        ml_stmt_atomic(comment("compute the hash value of the input string"),
            Context),
        ml_stmt_atomic(assign(SlotVarLval,
            ml_binop(bitwise_and(int_type_int),
                ml_unop(HashOp, VarRval),
                ml_const(mlconst_int(HashMask)))),
            Context)
        ],
    FoundMatchCond =
        ml_binop(logical_and,
            ml_binop(int_cmp(int_type_int, ne), StringVarRval,
                ml_const(mlconst_null(StringVarType))),
            ml_binop(str_cmp(eq), StringVarRval, VarRval)
        ),
    LookForMatchPrepareStmts = [
        ml_stmt_atomic(comment("lookup the string for this hash slot"),
            Context),
        ml_stmt_atomic(assign(StringVarLval,
            ml_lval(ml_field(yes(ptag(0u8)),
                ml_vector_common_row_addr(VectorCommon, SlotVarRval),
                StructType, StringFieldId, StringVarType))),
            Context),
        ml_stmt_atomic(comment("did we find a match?"), Context)
    ],
    SlotTest = ml_binop(int_cmp(int_type_int, ge), SlotVarRval,
        ml_const(mlconst_int(0))),
    (
        MaybeStopLoopVarLval = no,
        InitStopLoopVarStmts = [],
        InitSuccessStmts = [],
        LoopTest = SlotTest
    ;
        MaybeStopLoopVarLval = yes(StopLoopVarLval),
        InitStopLoopVarStmt = ml_stmt_atomic(
            assign(StopLoopVarLval, ml_const(mlconst_int(0))),
            Context),
        InitStopLoopVarStmts = [InitStopLoopVarStmt],
        (
            CodeModel = model_det,
            % If the switch is model_det, the value of `succeeded'
            % is irrelevant, as it will not be consulted.
            InitSuccessStmts = []
        ;
            CodeModel = model_semi,
            ml_gen_set_success(ml_const(mlconst_false), Context,
                InitSuccessStmt, !Info),
            InitSuccessStmts = [InitSuccessStmt]
        ;
            CodeModel = model_non,
            % If the switch is model_non, we get to the end of search code
            % (which may or my not be a loop) for both matches and non-matches,
            % though in the case of matches we get there only after invoking
            % the success continuation for each solution.
            InitSuccessStmts = []
        ),
        StopLoopTest = ml_binop(int_cmp(int_type_int, eq),
            ml_lval(StopLoopVarLval), ml_const(mlconst_int(0))),
        LoopTest = ml_binop(logical_and, StopLoopTest, SlotTest)
    ),
    (
        MaybeNextSlotFieldId = yes(NextSlotFieldId),
        NoMatchStmt = ml_stmt_block([], [], [
            ml_stmt_atomic(comment(
                "no match yet, so get next slot in hash chain"),
                Context),
            ml_stmt_atomic(assign(SlotVarLval,
                ml_lval(ml_field(yes(ptag(0u8)),
                    ml_vector_common_row_addr(VectorCommon, SlotVarRval),
                    StructType, NextSlotFieldId, SlotVarType))),
                Context)
            ], Context),
        LookForMatchStmt =
            ml_stmt_if_then_else(FoundMatchCond, SuccessStmt,
                yes(NoMatchStmt), Context),
        LoopBody = ml_stmt_block([], [],
            LookForMatchPrepareStmts ++ [LookForMatchStmt], Context),
        LoopStmts = [
            ml_stmt_atomic(comment("hash chain loop"), Context),
            ml_stmt_while(loop_at_least_once, LoopTest, LoopBody,
                [SlotVarName, StringVarName | StopLoopVarNameList], Context)
        ],
        SearchStmts = PrepareForMatchStmts ++
            InitStopLoopVarStmts ++ InitSuccessStmts ++ LoopStmts,
        Stmts = [InitialCommentStmt | SetupForFailStmts] ++
            SearchStmts ++ AfterStmts
    ;
        MaybeNextSlotFieldId = no,
        NoLoopCommentStmt = ml_stmt_atomic(
            comment("no collisions; no hash chain loop"), Context),
        LookForMatchStmt =
            ml_stmt_if_then_else(FoundMatchCond, SuccessStmt, no, Context),
        SearchStmts = PrepareForMatchStmts ++ InitSuccessStmts ++
            [NoLoopCommentStmt | LookForMatchPrepareStmts] ++
            [LookForMatchStmt],
        Stmts = [InitialCommentStmt | SearchStmts] ++ AfterStmts
    ).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of jump binary search tables.
%

ml_generate_string_binary_jump_switch(VarRval, Cases, CodeModel, CanFail,
        EntryPackedArgsMap, Context, Defns, Stmts, !Info) :-
    ml_gen_string_binary_switch_search_vars(CodeModel, CanFail,
        Context, VarRval, BinarySearchInfo, !Info),
    BinarySearchInfo = ml_binary_search_info(_CodeModel,
        _VarRval, _LoVarNameType, _HiVarNameType, MidVarNameType,
        _ResultVarNameType, _MaybeStopLoopVarNameType, _FailStmts, Defns),

    MidVarNameType = mlds_local_var_name_type(MidVarName, MidVarType),
    MidVarLval = ml_local_var(MidVarName, MidVarType),

    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    ml_gen_info_get_target(!.Info, Target),

    MLDS_StringType = mlds_builtin_type_string,
    MLDS_CaseNumType = mlds_builtin_type_int(int_type_int),
    MLDS_ArgTypes = [MLDS_StringType, MLDS_CaseNumType],

    % Generate the binary search table.
    ml_gen_info_get_global_data(!.Info, GlobalData0),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        MLDS_ArgTypes, StructTypeNum, StructType, FieldIds,
        GlobalData0, GlobalData1),
    ml_gen_info_set_global_data(GlobalData1, !Info),
    (
        FieldIds = [StringFieldId, CaseNumFieldId]
    ;
        ( FieldIds = []
        ; FieldIds = [_]
        ; FieldIds = [_, _, _ | _]
        ),
        unexpected($pred, "bad FieldIds")
    ),
    map.init(CaseLabelMap0),
    string_binary_cases(Cases,
        ml_gen_tagged_case_code_for_string_switch_return_case_id(CodeModel,
            EntryPackedArgsMap),
        CaseLabelMap0, CaseLabelMap, [], ReachableConstVarMaps, !Info,
        unit, _, SortedTable),
    ml_gen_record_consensus_const_var_map(ReachableConstVarMaps, !Info),
    ml_gen_string_binary_jump_initializers(SortedTable, StructType,
        cord.init, RowInitializersCord, 0, TableSize),
    RowInitializers = cord.list(RowInitializersCord),
    % We need to get the globaldata out of !Info again, since the generation
    % of code for the switch arms can generate global data.
    ml_gen_info_get_global_data(!.Info, GlobalData2),
    ml_gen_static_vector_defn(MLDS_ModuleName, StructTypeNum, RowInitializers,
        VectorCommon, GlobalData2, GlobalData),
    ml_gen_info_set_global_data(GlobalData, !Info),

    % Generate the switch that uses the final value of MidVar (the one that,
    % when used as an index into the binary search table, matches the current
    % value of the switched-on variable) to select the piece of code to
    % execute.
    map.to_assoc_list(CaseLabelMap, CaseLabelList),
    ml_gen_string_binary_jump_switch_arms(CaseLabelList, [], SwitchCases0),
    list.sort(SwitchCases0, SwitchCases),
    SwitchStmt0 = ml_stmt_switch(mlds_builtin_type_int(int_type_int),
        ml_lval(ml_field(yes(ptag(0u8)),
            ml_vector_common_row_addr(VectorCommon, ml_lval(MidVarLval)),
            StructType, CaseNumFieldId, MLDS_StringType)),
        mlds_switch_range(0, TableSize - 1), SwitchCases,
        default_is_unreachable, Context),
    ml_simplify_switch(SwitchStmt0, SwitchStmt, !Info),

    % Generate the code that searches the table.
    InitialComment = "binary string jump switch",
    ml_gen_string_binary_switch_search(Context, InitialComment,
        BinarySearchInfo, VectorCommon, TableSize, StructType, StringFieldId,
        [], [SwitchStmt], Stmts, !Info).

:- pred ml_gen_string_binary_jump_initializers(assoc_list(string, case_id)::in,
    mlds_type::in,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out,
    int::in, int::out) is det.

ml_gen_string_binary_jump_initializers([],
        _StructType, !RowInitializersCord, !CurIndex).
ml_gen_string_binary_jump_initializers([Str - CaseId | StrCaseIds],
        StructType, !RowInitializersCord, !CurIndex) :-
    CaseId = case_id(CaseNum),
    StrRval = ml_const(mlconst_string(Str)),
    CaseNumRval = ml_const(mlconst_int(CaseNum)),
    RowInitializer = init_struct(StructType,
        [init_obj(StrRval), init_obj(CaseNumRval)]),
    cord.snoc(RowInitializer, !RowInitializersCord),
    !:CurIndex = !.CurIndex + 1,
    ml_gen_string_binary_jump_initializers(StrCaseIds,
        StructType, !RowInitializersCord, !CurIndex).

:- pred ml_gen_string_binary_jump_switch_arms(
    assoc_list(case_id, mlds_stmt)::in,
    list(mlds_switch_case)::in, list(mlds_switch_case)::out) is det.

ml_gen_string_binary_jump_switch_arms([], !SwitchCases).
ml_gen_string_binary_jump_switch_arms([CaseIdStmt | CaseIdsStmts],
        !SwitchCases) :-
    CaseIdStmt = CaseId - Stmt,
    CaseId = case_id(CaseNum),
    MatchCond = match_value(ml_const(mlconst_int(CaseNum))),
    SwitchCase = mlds_switch_case(MatchCond, [], Stmt),
    !:SwitchCases = [SwitchCase | !.SwitchCases],
    ml_gen_string_binary_jump_switch_arms(CaseIdsStmts, !SwitchCases).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of lookup binary search tables.
%

ml_generate_string_binary_lookup_switch(VarRval, TaggedCases, LookupSwitchInfo,
        CodeModel, CanFail, Context, Defns, Stmts, !:Info) :-
    LookupSwitchInfo =
        ml_lookup_switch_info(CaseIdConsts, OutVars, OutTypes, !:Info),
    (
        CaseIdConsts = all_one_soln(CaseIdValueMap),
        ml_case_id_soln_consts_to_tag_soln_consts(get_string_tag, TaggedCases,
            CaseIdValueMap, StrValueMap),
        map.to_assoc_list(StrValueMap, StrValues),
        ml_generate_string_binary_simple_lookup_switch(VarRval,
            StrValues, CodeModel, CanFail, OutVars, OutTypes,
            Context, Defns, Stmts, !Info)
    ;
        CaseIdConsts = some_several_solns(CaseIdSolnMap, _Unit),
        expect(unify(CodeModel, model_non), $pred, "CodeModel != model_non"),
        ml_case_id_soln_consts_to_tag_soln_consts(get_string_tag, TaggedCases,
            CaseIdSolnMap, StrSolnMap),
        map.to_assoc_list(StrSolnMap, StrSolns),
        ml_generate_string_binary_several_soln_lookup_switch(VarRval,
            StrSolns, CodeModel, CanFail, OutVars, OutTypes,
            Context, Defns, Stmts, !Info)
    ).

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

:- pred ml_generate_string_binary_simple_lookup_switch(mlds_rval::in,
    assoc_list(string, list(mlds_rval))::in, code_model::in, can_fail::in,
    list(prog_var)::in, list(mlds_type)::in, prog_context::in,
    list(mlds_local_var_defn)::out, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_generate_string_binary_simple_lookup_switch(VarRval, CaseValues0,
        CodeModel, CanFail, OutVars, OutTypes, Context, Defns, Stmts, !Info) :-
    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    ml_gen_info_get_target(!.Info, Target),

    MLDS_StringType = mercury_type_to_mlds_type(ModuleInfo, string_type),
    MLDS_ArgTypes = [MLDS_StringType | OutTypes],

    % Generate the binary search table.
    list.sort(CaseValues0, CaseValues),
    ml_gen_info_get_global_data(!.Info, GlobalData0),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        MLDS_ArgTypes, StructTypeNum, StructType, FieldIds,
        GlobalData0, GlobalData1),
    (
        FieldIds = [StringFieldId | OutFieldIds]
    ;
        FieldIds = [],
        unexpected($pred, "bad FieldIds")
    ),
    ml_gen_string_binary_simple_lookup_initializers(CaseValues, StructType,
        cord.init, RowInitializersCord, 0, TableSize),
    RowInitializers = cord.list(RowInitializersCord),
    ml_gen_static_vector_defn(MLDS_ModuleName, StructTypeNum, RowInitializers,
        VectorCommon, GlobalData1, GlobalData),
    ml_gen_info_set_global_data(GlobalData, !Info),

    ml_gen_string_binary_switch_search_vars(CodeModel, CanFail,
        Context, VarRval, BinarySearchInfo, !Info),
    BinarySearchInfo = ml_binary_search_info(_CodeModel, _VarRval,
        _LoVarNameType, _HiVarNameType, MidVarNameType,
        _ResultVarNameType, _MaybeStopLoopNameType, _FailStmts, Defns),

    MidVarNameType = mlds_local_var_name_type(MidVarName, MidVarType),
    MidVarLval = ml_local_var(MidVarName, MidVarType),
    MidVarRval = ml_lval(MidVarLval),

    ml_generate_field_assigns(OutVars, OutTypes, OutFieldIds,
        VectorCommon, StructType, MidVarRval, Context, GetArgStmts, !Info),
    (
        CodeModel = model_det,
        MatchStmts = GetArgStmts
    ;
        CodeModel = model_semi,
        ml_gen_set_success(ml_const(mlconst_true), Context, SetSuccessTrueStmt,
            !Info),
        MatchStmts = GetArgStmts ++ [SetSuccessTrueStmt]
    ;
        CodeModel = model_non,
        unexpected($pred, "model_non")
    ),

    % Generate the code that searches the table.
    InitialComment = "binary string simple lookup switch",
    ml_gen_string_binary_switch_search(Context, InitialComment,
        BinarySearchInfo, VectorCommon, TableSize, StructType, StringFieldId,
        [], MatchStmts, Stmts, !Info).

:- pred ml_gen_string_binary_simple_lookup_initializers(
    assoc_list(string, list(mlds_rval))::in, mlds_type::in,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out,
    int::in, int::out) is det.

ml_gen_string_binary_simple_lookup_initializers([],
        _StructType, !RowInitializersCord, !CurIndex).
ml_gen_string_binary_simple_lookup_initializers([Str - Rvals | StrRvals],
        StructType, !RowInitializersCord, !CurIndex) :-
    StrRval = ml_const(mlconst_string(Str)),
    RowInitializer = init_struct(StructType,
        [init_obj(StrRval) | list.map(wrap_init_obj, Rvals)]),
    cord.snoc(RowInitializer, !RowInitializersCord),
    !:CurIndex = !.CurIndex + 1,
    ml_gen_string_binary_simple_lookup_initializers(StrRvals,
        StructType, !RowInitializersCord, !CurIndex).

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

:- pred ml_generate_string_binary_several_soln_lookup_switch(mlds_rval::in,
    assoc_list(string, soln_consts(mlds_rval))::in,
    code_model::in, can_fail::in,
    list(prog_var)::in, list(mlds_type)::in, prog_context::in,
    list(mlds_local_var_defn)::out, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_generate_string_binary_several_soln_lookup_switch(VarRval, CaseSolns0,
        CodeModel, CanFail, OutVars, OutTypes, Context, Defns, Stmts, !Info) :-
    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    ml_gen_info_get_target(!.Info, Target),
    MLDS_StringType = mlds_builtin_type_string,
    MLDS_IntType = mlds_builtin_type_int(int_type_int),
    FirstSolnFieldTypes =
        [MLDS_StringType, MLDS_IntType, MLDS_IntType | OutTypes],
    LaterSolnFieldTypes = OutTypes,

    % Generate the binary search table.
    list.sort(CaseSolns0, CaseSolns),
    ml_gen_info_get_global_data(!.Info, GlobalData0),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        FirstSolnFieldTypes, FirstSolnStructTypeNum, FirstSolnStructType,
        FirstSolnFieldIds, GlobalData0, GlobalData1),
    ml_gen_static_vector_type(MLDS_ModuleName, Context, Target,
        LaterSolnFieldTypes, LaterSolnStructTypeNum, LaterSolnStructType,
        LaterSolnOutFieldIds, GlobalData1, GlobalData2),
    ( if
        FirstSolnFieldIds = [StringFieldIdPrime, NumLaterSolnsFieldIdPrime,
            FirstLaterSolnRowFieldIdPrime | FirstSolnOutFieldIdsPrime]
    then
        StringFieldId = StringFieldIdPrime,
        NumLaterSolnsFieldId = NumLaterSolnsFieldIdPrime,
        FirstLaterSolnRowFieldId = FirstLaterSolnRowFieldIdPrime,
        FirstSolnOutFieldIds = FirstSolnOutFieldIdsPrime
    else
        unexpected($pred, "bad FieldIds")
    ),
    ml_gen_string_binary_several_lookup_initializers(CaseSolns,
        FirstSolnStructType, LaterSolnStructType,
        cord.init, FirstSolnRowInitializersCord,
        cord.init, LaterSolnRowInitializersCord,
        0, FirstSolnTableSize, 0),
    FirstSolnRowInitializers = cord.list(FirstSolnRowInitializersCord),
    LaterSolnRowInitializers = cord.list(LaterSolnRowInitializersCord),
    ml_gen_static_vector_defn(MLDS_ModuleName, FirstSolnStructTypeNum,
        FirstSolnRowInitializers, FirstSolnVectorCommon,
        GlobalData2, GlobalData3),
    ml_gen_static_vector_defn(MLDS_ModuleName, LaterSolnStructTypeNum,
        LaterSolnRowInitializers, LaterSolnVectorCommon,
        GlobalData3, GlobalData),
    ml_gen_info_set_global_data(GlobalData, !Info),

    ml_gen_string_binary_switch_search_vars(CodeModel, CanFail,
        Context, VarRval, BinarySearchInfo, !Info),
    BinarySearchInfo = ml_binary_search_info(_CodeModel, _VarRval,
        _LoVarNameType, _HiVarNameType, MidVarNameType,
        _ResultVarNameType, _MaybeStopLoopNameType, _FailStmts, Defns),
    MidVarNameType = mlds_local_var_name_type(MidVarName, MidVarType),
    MidVarLval = ml_local_var(MidVarName, MidVarType),

    ml_gen_several_soln_lookup_code(Context,
        ml_lval(MidVarLval), OutVars, OutTypes,
        FirstSolnStructType, LaterSolnStructType,
        NumLaterSolnsFieldId, FirstLaterSolnRowFieldId,
        FirstSolnOutFieldIds, LaterSolnOutFieldIds,
        FirstSolnVectorCommon, LaterSolnVectorCommon,
        do_not_need_bit_vec_check_no_gaps,
        MatchDefns, SuccessStmts, !Info),

    % Generate the code that searches the table.
    InitialComment = "binary string several soln lookup switch",
    ml_gen_string_binary_switch_search(Context, InitialComment,
        BinarySearchInfo, FirstSolnVectorCommon,FirstSolnTableSize,
        FirstSolnStructType, StringFieldId,
        MatchDefns, SuccessStmts, Stmts, !Info).

:- pred ml_gen_string_binary_several_lookup_initializers(
    assoc_list(string, soln_consts(mlds_rval))::in,
    mlds_type::in, mlds_type::in,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out,
    cord(mlds_initializer)::in, cord(mlds_initializer)::out,
    int::in, int::out, int::in) is det.

ml_gen_string_binary_several_lookup_initializers([],
        _FirstSolnStructType, _LaterSolnStructType,
        !FirstSolnRowInitializersCord, !LaterSolnRowInitializersCord,
        !CurFirstSolnIndex, _CurLaterSolnIndex).
ml_gen_string_binary_several_lookup_initializers([Str - Solns | StrSolns],
        FirstSolnStructType, LaterSolnStructType,
        !FirstSolnRowInitializersCord, !LaterSolnRowInitializersCord,
        !CurFirstSolnIndex, !.CurLaterSolnIndex) :-
    StrRval = ml_const(mlconst_string(Str)),
    (
        Solns = one_soln(FirstSolnRvals),
        NumLaterSolnsInitializer = gen_init_int(0),
        FirstLaterSlotInitializer = gen_init_int(-1),
        FirstSolnRowInitializer = init_struct(FirstSolnStructType,
            [init_obj(StrRval),
                NumLaterSolnsInitializer, FirstLaterSlotInitializer
                | list.map(wrap_init_obj, FirstSolnRvals)])
    ;
        Solns = several_solns(FirstSolnRvals, LaterSolns),
        list.length(LaterSolns, NumLaterSolns),
        NumLaterSolnsInitializer = gen_init_int(NumLaterSolns),
        FirstLaterSlotInitializer = gen_init_int(!.CurLaterSolnIndex),
        FirstSolnRowInitializer = init_struct(FirstSolnStructType,
            [init_obj(StrRval),
                NumLaterSolnsInitializer, FirstLaterSlotInitializer
                | list.map(wrap_init_obj, FirstSolnRvals)]),
        LaterSolnRowInitializers = list.map(
            ml_construct_later_soln_row(LaterSolnStructType),
            LaterSolns),
        !:LaterSolnRowInitializersCord = !.LaterSolnRowInitializersCord ++
            from_list(LaterSolnRowInitializers),
        !:CurLaterSolnIndex = !.CurLaterSolnIndex + NumLaterSolns
    ),
    cord.snoc(FirstSolnRowInitializer, !FirstSolnRowInitializersCord),
    !:CurFirstSolnIndex = !.CurFirstSolnIndex + 1,
    ml_gen_string_binary_several_lookup_initializers(StrSolns,
        FirstSolnStructType, LaterSolnStructType,
        !FirstSolnRowInitializersCord, !LaterSolnRowInitializersCord,
        !CurFirstSolnIndex, !.CurLaterSolnIndex).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% Code useful for both jump and lookup binary string switches.
%

:- type ml_binary_search_info
    --->    ml_binary_search_info(
                mbsi_code_model             :: code_model,
                mbsi_switch_var             :: mlds_rval,
                mbsi_lo_var                 :: mlds_local_var_name_type,
                mbsi_hi_var                 :: mlds_local_var_name_type,
                mbsi_mid_var                :: mlds_local_var_name_type,
                mbsi_result_var             :: mlds_local_var_name_type,
                mbsi_stop_loop_var          :: maybe(mlds_local_var_name_type),
                mbsi_fail_statements        :: list(mlds_stmt),
                mbsi_defns                  :: list(mlds_local_var_defn)
            ).

:- pred ml_gen_string_binary_switch_search_vars(code_model::in, can_fail::in,
    prog_context::in, mlds_rval::in, ml_binary_search_info::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_string_binary_switch_search_vars(CodeModel, CanFail,
        Context, VarRval, BinarySearchInfo, !Info) :-
    % Generate the following local variable declarations:
    %   int         lo;
    %   int         hi;
    %   int         mid;
    %   MR_String   str;

    IndexType = mlds_builtin_type_int(int_type_int),
    ResultType = mlds_builtin_type_int(int_type_int),
    % We never need to trace ints.
    IndexGCStmt = gc_no_stmt,
    ResultGCStmt = gc_no_stmt,

    ml_gen_info_new_aux_var_name(mcav_lo, LoVarName, !Info),
    LoVarDefn = ml_gen_mlds_var_decl(LoVarName, IndexType,
        IndexGCStmt, Context),
    LoVarNameType = mlds_local_var_name_type(LoVarName, IndexType),

    ml_gen_info_new_aux_var_name(mcav_hi, HiVarName, !Info),
    HiVarDefn = ml_gen_mlds_var_decl(HiVarName, IndexType,
        IndexGCStmt, Context),
    HiVarNameType = mlds_local_var_name_type(HiVarName, IndexType),

    ml_gen_info_new_aux_var_name(mcav_mid, MidVarName, !Info),
    MidVarDefn = ml_gen_mlds_var_decl(MidVarName, IndexType,
        IndexGCStmt, Context),
    MidVarNameType = mlds_local_var_name_type(MidVarName, IndexType),

    ml_gen_info_new_aux_var_name(mcav_result, ResultVarName, !Info),
    ResultVarDefn = ml_gen_mlds_var_decl(ResultVarName, ResultType,
        ResultGCStmt, Context),
    ResultVarNameType = mlds_local_var_name_type(ResultVarName, ResultType),

    AlwaysDefns = [LoVarDefn, HiVarDefn, MidVarDefn, ResultVarDefn],
    ml_should_use_stop_loop(Context, yes,
        MaybeStopLoopNameType, StopLoopVarDefns, !Info),
    Defns = AlwaysDefns ++ StopLoopVarDefns,

    % Generate the code for when the lookup fails.
    ml_gen_maybe_switch_failure(CodeModel, CanFail, Context, FailStmts, !Info),

    BinarySearchInfo = ml_binary_search_info(CodeModel, VarRval,
        LoVarNameType, HiVarNameType, MidVarNameType,
        ResultVarNameType, MaybeStopLoopNameType, FailStmts, Defns).

:- pred ml_gen_string_binary_switch_search(prog_context::in, string::in,
    ml_binary_search_info::in, mlds_vector_common::in, int::in, mlds_type::in,
    mlds_field_id::in, list(mlds_local_var_defn)::in, list(mlds_stmt)::in,
    list(mlds_stmt)::out, ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_string_binary_switch_search(Context, InitialComment,
        BinarySearchInfo, VectorCommon, TableSize, StructType, StringFieldId,
        MatchDefns, MatchStmt, Stmts, !Info) :-
    BinarySearchInfo = ml_binary_search_info(CodeModel, VarRval,
        LoVarNameType, HiVarNameType, MidVarNameType,
        ResultVarNameType, MaybeStopLoopVarNameType, FailStmts, _Defns),

    LoVarNameType = mlds_local_var_name_type(LoVarName, LoVarType),
    LoVarLval = ml_local_var(LoVarName, LoVarType),
    LoVarRval = ml_lval(LoVarLval),

    HiVarNameType = mlds_local_var_name_type(HiVarName, HiVarType),
    HiVarLval = ml_local_var(HiVarName, HiVarType),
    HiVarRval = ml_lval(HiVarLval),

    MidVarNameType = mlds_local_var_name_type(MidVarName, MidVarType),
    MidVarLval = ml_local_var(MidVarName, MidVarType),
    MidVarRval = ml_lval(MidVarLval),

    ResultVarNameType = mlds_local_var_name_type(ResultVarName, ResultVarType),
    ResultVarLval = ml_local_var(ResultVarName, ResultVarType),
    ResultVarRval = ml_lval(ResultVarLval),

    (
        MaybeStopLoopVarNameType = no,
        MaybeStopLoopVarLval = no,
        StopLoopVarNameList = []
    ;
        MaybeStopLoopVarNameType = yes(StopLoopVarNameType),
        StopLoopVarNameType =
            mlds_local_var_name_type(StopLoopVarName, StopLoopVarType),
        StopLoopVarLval0 = ml_local_var(StopLoopVarName, StopLoopVarType),
        MaybeStopLoopVarLval = yes(StopLoopVarLval0),
        StopLoopVarNameList = [StopLoopVarName]
    ),

    ml_wrap_loop_break(CodeModel, yes, Context, MaybeStopLoopVarLval,
        MatchDefns, MatchStmt, FailStmts,
        SetupForFailStmts, SuccessStmt, AfterStmts, !Info),

    ml_gen_info_get_module_info(!.Info, ModuleInfo),
    MLDS_StringType = mercury_type_to_mlds_type(ModuleInfo, string_type),

    InitLoVarStmt = ml_stmt_atomic(
        assign(LoVarLval, ml_const(mlconst_int(0))),
        Context),
    InitHiVarStmt = ml_stmt_atomic(
        assign(HiVarLval, ml_const(mlconst_int(TableSize - 1))),
        Context),
    CrossingTest = ml_binop(int_cmp(int_type_int, le), LoVarRval, HiVarRval),

    AssignMidVarStmt = ml_stmt_atomic(
        % NOTE This assumes that LoVarRval + HiVarRval cannot overflow.
        % This is a safe assumption; I (zs) am pretty sure that if we ever
        % get a string switch with around 2^30 cases, then the compiler
        % will run out of memory before we get here :-(
        assign(MidVarLval,
            ml_binop(int_arith(int_type_int, ao_div),
                ml_binop(int_arith(int_type_int, ao_add),
                    LoVarRval, HiVarRval),
                ml_const(mlconst_int(2)))),
        Context),
    AssignResultVarStmt = ml_stmt_atomic(
        assign(ResultVarLval,
            ml_binop(str_nzp,
                VarRval,
                ml_lval(ml_field(yes(ptag(0u8)),
                    ml_vector_common_row_addr(VectorCommon, MidVarRval),
                    StructType, StringFieldId, MLDS_StringType)))),
        Context),
    ResultTest = ml_binop(int_cmp(int_type_int, eq), ResultVarRval,
        ml_const(mlconst_int(0))),
    UpdateLoOrHiVarStmt =
        ml_stmt_if_then_else(
            ml_binop(int_cmp(int_type_int, lt), ResultVarRval,
                ml_const(mlconst_int(0))),
            ml_stmt_atomic(
                assign(HiVarLval,
                    ml_binop(int_arith(int_type_int, ao_sub), MidVarRval,
                        ml_const(mlconst_int(1)))),
                Context),
            yes(ml_stmt_atomic(
                assign(LoVarLval,
                    ml_binop(int_arith(int_type_int, ao_add), MidVarRval,
                        ml_const(mlconst_int(1)))),
                Context)),
            Context),

    LoHiMidResultMaybeStopVarNames = [LoVarName, MidVarName, HiVarName,
        ResultVarName | StopLoopVarNameList],
    (
        MaybeStopLoopVarLval = no,
        LoopBodyStmts = [
            AssignMidVarStmt,
            AssignResultVarStmt,
            ml_stmt_if_then_else(ResultTest,
                SuccessStmt, yes(UpdateLoOrHiVarStmt), Context)
        ],
        SearchStmts = [
            InitLoVarStmt,
            InitHiVarStmt,
            ml_stmt_while(loop_at_least_once,
                CrossingTest,
                ml_stmt_block([], [], LoopBodyStmts, Context),
                LoHiMidResultMaybeStopVarNames, Context)
        ]
    ;
        MaybeStopLoopVarLval = yes(StopLoopVarLval),
        InitStopLoopVarStmt = ml_stmt_atomic(
            assign(StopLoopVarLval, ml_const(mlconst_int(0))),
            Context),
        StopLoopTest = ml_binop(int_cmp(int_type_int, eq),
            ml_lval(StopLoopVarLval), ml_const(mlconst_int(0))),
        LoopBodyStmts = [
            AssignMidVarStmt,
            AssignResultVarStmt,
            % SuccessStmt should set StopLoopVarLval to 1.
            ml_stmt_if_then_else(ResultTest,
                SuccessStmt, yes(UpdateLoOrHiVarStmt), Context)
        ],
        SearchStmts = [
            InitLoVarStmt,
            InitHiVarStmt,
            InitStopLoopVarStmt,
            ml_stmt_while(loop_at_least_once,
                ml_binop(logical_and, StopLoopTest, CrossingTest),
                ml_stmt_block([], [], LoopBodyStmts, Context),
                LoHiMidResultMaybeStopVarNames, Context)
        ]
    ),

    InitialCommentStmt = ml_stmt_atomic(comment(InitialComment), Context),
    Stmts = [InitialCommentStmt | SetupForFailStmts] ++
        SearchStmts ++ AfterStmts.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% Code useful for more than one kind of string switch.
%

:- pred ml_should_use_stop_loop(prog_context::in, bool::in,
    maybe(mlds_local_var_name_type)::out, list(mlds_local_var_defn)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_should_use_stop_loop(Context, LoopPresent,
        MaybeStopLoopVarNameType, StopLoopLvalDefns, !Info) :-
    (
        LoopPresent = no,
        UseStopLoop = no
    ;
        LoopPresent = yes,
        ml_gen_info_get_module_info(!.Info, ModuleInfo),
        module_info_get_globals(ModuleInfo, Globals),
        SupportsGoto = globals_target_supports_goto(Globals),
        globals.lookup_string_option(Globals, experiment, Experiment),
        (
            SupportsGoto = yes,
            ( if Experiment = "use_stop_loop" then
                UseStopLoop = yes
            else
                UseStopLoop = no
            )
        ;
            SupportsGoto = no,
            UseStopLoop = yes
        )
    ),
    (
        UseStopLoop = no,
        MaybeStopLoopVarNameType = no,
        StopLoopLvalDefns = []
    ;
        UseStopLoop = yes,
        % On targets that do not support gotos or break, after we have
        % handled a match, we set the stop loop flag, which will cause the
        % next test of the loop condition to fail.

        StopLoopType = mlds_builtin_type_int(int_type_int),
        % We never need to trace ints.
        StopLoopGCStmt = gc_no_stmt,

        ml_gen_info_new_aux_var_name(mcav_stop_loop, StopLoopVarName, !Info),
        StopLoopVarDefn = ml_gen_mlds_var_decl(StopLoopVarName,
            StopLoopType, StopLoopGCStmt, Context),
        StopLoopVarNameType =
            mlds_local_var_name_type(StopLoopVarName, StopLoopType),
        MaybeStopLoopVarNameType = yes(StopLoopVarNameType),
        StopLoopLvalDefns = [StopLoopVarDefn]
    ).

:- pred ml_gen_maybe_switch_failure(code_model::in, can_fail::in,
    prog_context::in, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_gen_maybe_switch_failure(CodeModel, CanFail, Context, FailStmts, !Info) :-
    % We used to include comments in FailStmts. However, that would complicate
    % the task of ml_wrap_loop_break, which needs to decide whether FailStmts
    % actually contains executable code.
    (
        CanFail = cannot_fail,
        % This can happen if the initial inst of the switched-on variable
        % shows that we know a finite set of strings that the variable can be
        % bound to.
        FailStmts = []
    ;
        CanFail = can_fail,
        ml_gen_failure(CodeModel, Context, FailStmts, !Info)
    ).

    % ml_wrap_loop_break(CodeModel, LoopPresent,
    %   Context, MaybeStopLoopVarLval, MatchDefns, MatchStmts,
    %   SetupForFailStmts, BodyStmt, AfterStmts, !Info)
    %
    % MatchStmts should be the statements that we execute once we find
    % a match, and OnlyFailAfterStmts should be the statements that we
    % want to execute after the search loop if the loop did NOT find a match.
    %
    % This predicate wraps up MatchStmts with both MatchDefns and with
    % other following code that causes execution to exit the loop
    % after a match, and returns the resulting code as BodyStmt.
    %
    % We also return SetupForFailStmts and AfterStmts.
    % SetupForFailStmts will be code to put before the loop, to set up
    % for possible failure to find a match.
    %
    % AfterStmts will be code to put after the loop. It will contain
    % OnlyFailAfterStmts, wrapped up in a test if necessary, as well as
    % any code needed to enable BodyStmt to break out of the loop on a match.
    %
:- pred ml_wrap_loop_break(code_model::in, bool::in, prog_context::in,
    maybe(mlds_lval)::in, list(mlds_local_var_defn)::in,
    list(mlds_stmt)::in, list(mlds_stmt)::in,
    list(mlds_stmt)::out, mlds_stmt::out, list(mlds_stmt)::out,
    ml_gen_info::in, ml_gen_info::out) is det.

ml_wrap_loop_break(CodeModel, LoopPresent, Context, MaybeStopLoopVarLval,
        MatchDefns, MatchStmts, FailStmts,
        SetupForFailStmts, BodyStmt, AfterStmts, !Info) :-
    (
        CodeModel = model_det,
        SetupForFailStmts = [],
        expect(unify(FailStmts, []), $pred,
            "model_det, but FailStmts is not empty"),
        OnlyFailAfterStmts = []
    ;
        CodeModel = model_semi,
        (
            MaybeStopLoopVarLval = no,
            SetupForFailStmts = [],
            OnlyFailAfterStmts = FailStmts
        ;
            MaybeStopLoopVarLval = yes(_),
            % It is more efficient to set up the default value of the succeeded
            % variable (FALSE) with an unconditional assignment than it is
            % to set up its actual value with a conditional assignment.
            SetupForFailStmts = FailStmts,
            OnlyFailAfterStmts = []
        )
    ;
        CodeModel = model_non,
        SetupForFailStmts = [],
        expect(unify(FailStmts, []), $pred,
            "model_non, but FailStmts is not empty"),
        OnlyFailAfterStmts = []
    ),
    (
        MaybeStopLoopVarLval = no,
        ( if
            LoopPresent = no,
            OnlyFailAfterStmts = []
        then
            BodyStmt = ml_stmt_block(MatchDefns, [], MatchStmts, Context),
            AfterStmts = []
        else
            ml_gen_info_get_module_info(!.Info, ModuleInfo),
            module_info_get_globals(ModuleInfo, Globals),
            SupportsBreakContinue =
                globals_target_supports_break_and_continue(Globals),
            globals.lookup_string_option(Globals, experiment, Experiment),
            ( if
                SupportsBreakContinue = yes,
                OnlyFailAfterStmts = [],
                Experiment \= "use_end_label"
            then
                BreakCommentStmt = ml_stmt_atomic(
                    comment("break out of search loop"), Context),
                BreakStmt = ml_stmt_goto(goto_break_loop, Context),
                BodyStmt =
                    ml_stmt_block(MatchDefns, [],
                        MatchStmts ++ [BreakCommentStmt, BreakStmt], Context),
                AfterStmts = []
            else
                ml_gen_new_label(EndLabel, !Info),
                GotoCommentStmt = ml_stmt_atomic(
                    comment("jump out of search loop"), Context),
                GotoEndStmt = ml_stmt_goto(goto_label(EndLabel), Context),
                BodyStmt =
                    ml_stmt_block(MatchDefns, [],
                        MatchStmts ++ [GotoCommentStmt, GotoEndStmt], Context),
                EndLabelStmt = ml_stmt_label(EndLabel, Context),
                AfterStmts = OnlyFailAfterStmts ++ [EndLabelStmt]
            )
        )
    ;
        MaybeStopLoopVarLval = yes(StopLoopVarLval),
        ( if
            LoopPresent = no,
            OnlyFailAfterStmts = []
        then
            BodyStmt =
                ml_stmt_block(MatchDefns, [], MatchStmts, Context)
        else
            SetStopLoopStmt =
                ml_stmt_atomic(
                    assign(StopLoopVarLval, ml_const(mlconst_int(1))),
                    Context),
            BodyStmt =
                ml_stmt_block(MatchDefns, [],
                    MatchStmts ++ [SetStopLoopStmt], Context)
        ),
        (
            OnlyFailAfterStmts = [],
            AfterStmts = []
        ;
            (
                OnlyFailAfterStmts = [OnlyFailAfterStmt]
            ;
                OnlyFailAfterStmts = [_, _ | _],
                OnlyFailAfterStmt =
                    ml_stmt_block([], [], OnlyFailAfterStmts, Context)
            ),
            SuccessTest = ml_binop(int_cmp(int_type_int, eq),
                ml_lval(StopLoopVarLval),
                ml_const(mlconst_int(0))),
            AfterStmt =
                ml_stmt_if_then_else(SuccessTest, OnlyFailAfterStmt, no,
                    Context),
            AfterStmts = [AfterStmt]
        )
    ).

%---------------------------------------------------------------------------%
:- end_module ml_backend.ml_string_switch.
%---------------------------------------------------------------------------%
