Files
mercury/compiler/string_switch.m
Zoltan Somogyi b3e51a321a Add a guard to an optimization of computed gotos.
compiler/llds.m:
    Add a field to the computed_goto LLDS instruction that specifies
    the maximum value the index rval, if that maximum is known.

compiler/peephole.m:
    I recently add an optimization that replaces a computed_goto that has
    two possible targets with an if-then-else, with the condition being
    a bitmap in which the index rval selects a bit position. As Peter
    pointed out, this is unsafe if the index rval can exceed the length
    of the bitmap, which is 32 bits, so apply this optimization only if
    (a) the maximum possible value of the index rval is known, and
    (b) it is less than 32.

    In cases where this extra test makes that optimization inapplicable,
    try to apply a simple X = val1 or X = val2 test.

compiler/code_util.m:
compiler/dense_switch.m:
compiler/dupelim.m:
compiler/dupproc.m:
compiler/exprn_aux.m:
compiler/frameopt.m:
compiler/global_data.m:
compiler/jumpopt.m:
compiler/livemap.m:
compiler/llds_out_file.m:
compiler/llds_out_instr.m:
compiler/middle_rec.m:
compiler/opt_debug.m:
compiler/opt_util.m:
compiler/reassign.m:
compiler/string_switch.m:
compiler/tag_switch.m:
compiler/transform_llds.m:
compiler/use_local_vars.m:
    Conform to the changes above.
2024-07-14 17:10:23 +02:00

1758 lines
75 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1994-2007, 2009-2011 The University of Melbourne.
% Copyright (C) 2015, 2017-2018, 2020, 2022, 2024 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: 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).
%
% When the switch arms are general code, what we put into the hash table
% or binary search table for each case is the offset of the relevant arm
% in a computed_goto. The generated code would be faster (due to better
% locality) if we included the actual target address instead. Unfortunately,
% that would require two extensions to the LLDS. The first and relatively
% easy change would be a new LLDS instruction that represents a goto
% to an arbitrary rval (in this case, the rval taken from the selected
% table row). The second and substantially harder change would be making
% the internal labels of the switch arms actually storable in static data.
% We do not currently have any way to refer to internal labels from data,
% and optimizations that manipulate labels (such as frameopt, which can
% duplicate them, and dupelim, which can replace them with other labels)
% would have to be taught to reflect any changes they make in the global
% data. It is the last step that is the killer in terms of difficulty
% of implementation.
%
% One possible way around the problem would be to do the code generation
% and optimization as we do now, just recording a bit more information
% during code generation about which numbers in static data refer to
% which computed_gotos, and then, after all the optimizations are done,
% to go back and replace all the indicated numbers with the corresponding
% final labels.
%
% WARNING: the code here is quite similar to the code in ml_string_switch.m.
% Any changes here may require similar changes there, and vice versa.
%
%---------------------------------------------------------------------------%
:- module ll_backend.string_switch.
:- interface.
:- import_module hlds.
:- import_module hlds.code_model.
:- import_module hlds.hlds_goal.
:- import_module ll_backend.code_info.
:- import_module ll_backend.code_loc_dep.
:- import_module ll_backend.llds.
:- import_module ll_backend.lookup_switch.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module list.
%---------------------%
:- pred generate_string_trie_jump_switch(rval::in, string::in,
list(tagged_case)::in, code_model::in, can_fail::in, hlds_goal_info::in,
label::in, branch_end::out, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
:- pred generate_string_trie_lookup_switch(rval::in,
lookup_switch_info(string)::in, can_fail::in, label::in,
branch_end::out, llds_code::out, code_info::out) is det.
%---------------------%
:- pred generate_string_hash_jump_switch(rval::in, string::in,
list(tagged_case)::in, code_model::in, can_fail::in, hlds_goal_info::in,
label::in, branch_end::out, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
:- pred generate_string_hash_lookup_switch(rval::in,
lookup_switch_info(string)::in, can_fail::in, label::in,
branch_end::out, llds_code::out, code_info::out) is det.
%---------------------%
:- pred generate_string_binary_jump_switch(rval::in, string::in,
list(tagged_case)::in, code_model::in, can_fail::in, hlds_goal_info::in,
label::in, branch_end::out, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
:- pred generate_string_binary_lookup_switch(rval::in,
lookup_switch_info(string)::in, can_fail::in, label::in,
branch_end::out, llds_code::out, code_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_llds.
:- import_module libs.
:- import_module libs.globals.
:- import_module ll_backend.code_util.
:- import_module ll_backend.lookup_util.
:- import_module ll_backend.switch_case.
:- import_module assoc_list.
:- import_module cord.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module string.
:- import_module unit.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
generate_string_trie_jump_switch(VarRval, VarName, TaggedCases,
CodeModel, CanFail, SwitchGoalInfo, EndLabel, MaybeEnd, Code,
!CI, CLD) :-
init_string_trie_switch_info_jump(CanFail, JumpInfo0, !CI, CLD),
BranchStart = JumpInfo0 ^ stsij_branch_start,
Params = represent_params(VarName, SwitchGoalInfo, CodeModel, BranchStart,
EndLabel),
% Generate code for the cases, and remember the label of each case.
map.init(CaseIdToLabelMap0),
map.init(CaseLabelMap0),
represent_tagged_cases_in_string_trie_switch(Params, TaggedCases,
CaseIdToLabelMap0, CaseIdToLabelMap, CaseLabelMap0, CaseLabelMap,
no, MaybeEnd, !CI),
JumpInfo = JumpInfo0 ^ stsij_case_id_to_label_map := CaseIdToLabelMap,
Info = stsi_jump(JumpInfo),
build_str_case_id_list(TaggedCases, _MaxCaseNum, StrsCaseIds),
create_nested_trie_switch(Info, VarRval, StrsCaseIds, TrieCode, !CI),
% Generate the code for the cases.
add_not_yet_included_cases(CasesCode, CaseLabelMap, _),
FailLabel = JumpInfo ^ stsij_fail_label,
FailLabelCode = singleton(
llds_instr(label(FailLabel), "fail label")
),
FailCode = JumpInfo ^ stsij_fail_code,
MainCode = TrieCode ++ CasesCode ++ FailLabelCode ++ FailCode,
SwitchKindStr = "string trie jump switch",
add_switch_kind_comment_and_end_label(SwitchKindStr, EndLabel,
MainCode, Code).
:- pred represent_tagged_cases_in_string_trie_switch(represent_params::in,
list(tagged_case)::in, map(case_id, label)::in, map(case_id, label)::out,
case_label_map::in, case_label_map::out,
branch_end::in, branch_end::out, code_info::in, code_info::out) is det.
represent_tagged_cases_in_string_trie_switch(_, [],
!CaseIdToLabelMap, !CaseLabelMap, !MaybeEnd, !CI).
represent_tagged_cases_in_string_trie_switch(Params,
[TaggedCase | TaggedCases],
!CaseIdToLabelMap, !CaseLabelMap, !MaybeEnd, !CI) :-
represent_tagged_case_for_llds(Params, TaggedCase, Label,
!CaseLabelMap, !MaybeEnd, !CI, unit, _),
TaggedCase = tagged_case(_, _, CaseId, _),
map.det_insert(CaseId, Label, !CaseIdToLabelMap),
represent_tagged_cases_in_string_trie_switch(Params, TaggedCases,
!CaseIdToLabelMap, !CaseLabelMap, !MaybeEnd, !CI).
%---------------------%
:- type code_unit_to_action
---> code_unit_to_action(int, code_unit_action).
:- type code_unit_action
---> action_nested_trie(label)
; action_case(case_id).
:- pred create_nested_trie_switch(string_trie_switch_info::in, rval::in,
assoc_list(string, case_id)::in, llds_code::out,
code_info::in, code_info::out) is det.
create_nested_trie_switch(Info, VarRval, StrsCaseIds, TrieCode, !CI) :-
get_encoding(Info, Encoding),
create_trie(Encoding, StrsCaseIds, TopTrieNode),
convert_trie_to_nested_switches(Info, VarRval, 0,
TopTrieNode, TopTrieLabel, TrieCode0, !CI),
( if
cord.head_tail(TrieCode0, HeadTrieCodeInstr, TailTrieCodeInstrs),
HeadTrieCodeInstr = llds_instr(label(TopTrieLabel), _)
then
TrieCode = TailTrieCodeInstrs
else
unexpected($pred, "TrieCode0 does not start with TopTrieLabel")
).
:- pred convert_trie_to_nested_switches(string_trie_switch_info::in,
rval::in, int::in, trie_node::in,
label::out, llds_code::out, code_info::in, code_info::out) is det.
convert_trie_to_nested_switches(Info, VarRval, NumMatched, TrieNode,
TrieNodeLabel, Code, !CI) :-
get_next_label(TrieNodeLabel, !CI),
(
TrieNode = trie_leaf(RevMatchedCodeUnits, NotYetMatchedCodeUnits,
CaseId),
list.reverse(RevMatchedCodeUnits, MatchedCodeUnits),
AllCodeUnits = MatchedCodeUnits ++ NotYetMatchedCodeUnits,
list.length(MatchedCodeUnits, NumMatchedCodeUnits),
expect(unify(NumMatchedCodeUnits, NumMatched), $pred,
"NumevMatchedCodeUnits != NumMatched"),
get_encoding(Info, Encoding),
det_from_code_unit_list_in_encoding_allow_ill_formed(Encoding,
AllCodeUnits, EndStr),
NodeComment = "AllCodeUnits " ++ string.string(AllCodeUnits),
CondRval = binop(offset_str_eq(NumMatched, no_size),
VarRval, const(llconst_string(EndStr))),
generate_trie_case_or_fall_through(Info, CondRval, CaseId,
CaseRepCode, !CI),
generate_trie_goto_fail_code(Info, GotoFailCode),
TrieNodeLabelCode = singleton(
llds_instr(label(TrieNodeLabel), NodeComment)
),
Code = TrieNodeLabelCode ++ CaseRepCode ++ GotoFailCode
;
TrieNode = trie_choice(RevMatchedCodeUnits, _ChoiceMap, MaybeEnd),
list.length(RevMatchedCodeUnits, NumRevMatchedCodeUnits),
expect(unify(NumRevMatchedCodeUnits, NumMatched), $pred,
"NumRevMatchedCodeUnits != NumMatched"),
get_code_unit_reg(Info, CodeUnitRegLval),
GetCurCodeUnitRval = binop(string_unsafe_index_code_unit,
VarRval, const(llconst_int(NumMatched))),
LabelCode = singleton(
llds_instr(label(TrieNodeLabel), "")
),
SetCodeUnitCode = singleton(
llds_instr(assign(CodeUnitRegLval, GetCurCodeUnitRval), "")
),
CodeUnitRval = lval(CodeUnitRegLval),
generate_trie_goto_fail_code(Info, GotoFailCode),
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),
get_encoding(Info, Encoding),
MatchedStickCodeUnits = MatchedCodeUnits ++ StickCodeUnits,
det_from_code_unit_list_in_encoding_allow_ill_formed(Encoding,
MatchedStickCodeUnits, MatchedStickStr),
TestComment =
"MatchedCodeUnits " ++ string.string(MatchedCodeUnits) ++
" StickCodeUnits " ++ string.string(StickCodeUnits),
TestRval = binop(CmpOp,
VarRval, const(llconst_string(MatchedStickStr))),
convert_trie_to_nested_switches(Info, VarRval,
NumMatched + NumStickCodeUnits,
TrieNodeAfterStick, TrieNodeLabelAfterStick,
CodeAfterStick, !CI),
TrieNodeCodeAddrAfterStick = code_label(TrieNodeLabelAfterStick),
TestCode = singleton(
llds_instr(if_val(TestRval, TrieNodeCodeAddrAfterStick),
TestComment)
),
TestChainCode = TestCode ++ GotoFailCode,
Code = LabelCode ++ TestChainCode ++ CodeAfterStick
;
( StickCodeUnits = []
; StickCodeUnits = [_]
),
convert_trie_choices_to_nested_switches(Info, VarRval,
NumMatched + 1, ChoicePairs,
cord.init, NestedTrieInfosCord, 0, NumActions0,
empty, NestedTrieCode, !CI),
NestedTrieInfos0 = cord.list(NestedTrieInfosCord),
(
MaybeEnd = no,
NestedTrieInfos = NestedTrieInfos0,
NumActions = NumActions0
;
MaybeEnd = yes(EndCaseId),
EndNestedTrieInfo =
code_unit_to_action(0, action_case(EndCaseId)),
NestedTrieInfos = [EndNestedTrieInfo | NestedTrieInfos0],
NumActions = NumActions0 + 1
),
( if NumActions =< 3 then
generate_nested_trie_try_chain(Info, CodeUnitRval,
NestedTrieInfos, empty, TestCode, !CI)
else
generate_nested_trie_binary_search(Info, CodeUnitRval,
NumActions, NestedTrieInfos, TestCode, !CI)
),
Code = LabelCode ++ SetCodeUnitCode ++ TestCode ++ NestedTrieCode
)
).
:- pred convert_trie_choices_to_nested_switches(string_trie_switch_info::in,
rval::in, int::in, assoc_list(int, trie_node)::in,
cord(code_unit_to_action)::in, cord(code_unit_to_action)::out,
int::in, int::out, llds_code::in, llds_code::out,
code_info::in, code_info::out) is det.
convert_trie_choices_to_nested_switches(_, _, _, [],
!CodeUnitToActionsCord, !NumActions, !NestedTrieCode, !CI).
convert_trie_choices_to_nested_switches(Info, VarRval,
NumMatched, [Choice | Choices],
!CodeUnitToActionsCord, !NumActions, !NestedTrieCode, !CI) :-
Choice = CodeUnit - TrieNode,
convert_trie_to_nested_switches(Info, VarRval, NumMatched,
TrieNode, TrieNodeLabel, TrieNodeCode, !CI),
CodeUnitToAction =
code_unit_to_action(CodeUnit, action_nested_trie(TrieNodeLabel)),
cord.snoc(CodeUnitToAction, !CodeUnitToActionsCord),
!:NumActions = !.NumActions + 1,
!:NestedTrieCode = !.NestedTrieCode ++ TrieNodeCode,
convert_trie_choices_to_nested_switches(Info, VarRval, NumMatched, Choices,
!CodeUnitToActionsCord, !NumActions, !NestedTrieCode, !CI).
%---------------------%
:- pred generate_nested_trie_try_chain(string_trie_switch_info::in, rval::in,
list(code_unit_to_action)::in, llds_code::in, llds_code::out,
code_info::in, code_info::out) is det.
generate_nested_trie_try_chain(Info, _, [], !TryChainCode, !CI) :-
generate_trie_goto_fail_code(Info, GotoFailCode),
!:TryChainCode = !.TryChainCode ++ GotoFailCode.
generate_nested_trie_try_chain(Info, CodeUnitRval,
[CodeUnitToAction | CodeUnitToActions], !TryChainCode, !CI) :-
CodeUnitToAction = code_unit_to_action(CodeUnit, Action),
CondRval = binop(int_cmp(int_type_int, eq),
CodeUnitRval, const(llconst_int(CodeUnit))),
(
Action = action_nested_trie(NestedTrieNodeLabel),
TestCodeUnitCode = singleton(
llds_instr(if_val(CondRval, code_label(NestedTrieNodeLabel)), "")
)
;
Action = action_case(CaseId),
generate_trie_case_or_fall_through(Info, CondRval, CaseId,
TestCodeUnitCode, !CI)
),
!:TryChainCode = !.TryChainCode ++ TestCodeUnitCode,
generate_nested_trie_try_chain(Info, CodeUnitRval,
CodeUnitToActions, !TryChainCode, !CI).
%---------------------%
:- pred generate_nested_trie_binary_search(string_trie_switch_info::in,
rval::in, int::in, list(code_unit_to_action)::in, llds_code::out,
code_info::in, code_info::out) is det.
generate_nested_trie_binary_search(Info, CodeUnitRval,
NumActions, CodeUnitToActions, TestCode, !CI) :-
( if NumActions =< 3 then
generate_nested_trie_try_chain(Info, CodeUnitRval,
CodeUnitToActions, empty, TestCode, !CI)
else
NumActionsR = NumActions / 2,
NumActionsL = NumActions - NumActionsR,
list.det_split_list(NumActionsL, CodeUnitToActions,
CodeUnitToActionsL, CodeUnitToActionsR),
list.det_head(CodeUnitToActionsR, HeadCodeUnitToActions),
HeadCodeUnitToActions = code_unit_to_action(LeastCodeUnitR, _),
code_info.get_next_label(LabelR, !CI),
TestRvalLR = binop(int_cmp(int_type_int, ge),
CodeUnitRval, const(llconst_int(LeastCodeUnitR))),
CommentLR = "binary search on code unit",
TestCodeLR = singleton(
llds_instr(if_val(TestRvalLR, code_label(LabelR)), CommentLR)
),
generate_nested_trie_binary_search(Info, CodeUnitRval,
NumActionsL, CodeUnitToActionsL, TestCodeL, !CI),
LabelCodeR = singleton(
llds_instr(label(LabelR), "")
),
generate_nested_trie_binary_search(Info, CodeUnitRval,
NumActionsR, CodeUnitToActionsR, TestCodeR, !CI),
TestCode = TestCodeLR ++ TestCodeL ++ LabelCodeR ++ TestCodeR
).
%---------------------------------------------------------------------------%
:- type string_trie_switch_info_jump
---> string_trie_switch_info_jump(
stsij_encoding :: string_encoding,
stsij_case_id_to_label_map :: map(case_id, label),
stsij_branch_start :: position_info,
stsij_code_unit_reg :: lval,
stsij_fail_label :: label,
stsij_fail_code :: llds_code,
stsij_goto_fail_code :: llds_code
).
:- pred init_string_trie_switch_info_jump(can_fail::in,
string_trie_switch_info_jump::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
init_string_trie_switch_info_jump(CanFail, Info, !CI, !.CLD) :-
Encoding = target_string_encoding(target_c),
% See the comment about acquire_reg/release_reg in the
% init_string_binary_switch_info predicate below.
acquire_reg(reg_r, CodeUnitReg, !CLD),
release_reg(CodeUnitReg, !CLD),
remember_position(!.CLD, BranchStart),
% We generate a fail label even if CanFail = cannot_fail, so that
% we have somewhere to go to throw an exception at runtime.
get_next_label(FailLabel, !CI),
% We must generate the failure code in the context in which
% none of the switch arms have been executed yet.
generate_string_switch_fail(CanFail, FailCode, !CI, !.CLD),
GotoFailCode = singleton(
llds_instr(goto(code_label(FailLabel)), "no match; goto fail")
),
% The code that creates the case_id to label map needs the
% other fields of the string_trie_switch_info_jump structure as its inputs,
% so this field will be filled in with *real* data by our caller.
map.init(CaseIdToLabelMap0),
Info = string_trie_switch_info_jump(Encoding, CaseIdToLabelMap0,
BranchStart, CodeUnitReg, FailLabel, FailCode, GotoFailCode).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
generate_string_trie_lookup_switch(VarRval, LookupSwitchInfo, CanFail,
EndLabel, !:MaybeEnd, Code, !:CI) :-
LookupSwitchInfo = lookup_switch_info(KeyToCaseIdMap, CaseConsts,
OutVars, OutTypes, EndBranch, !:MaybeEnd, !:CI, CLD),
init_string_trie_switch_info_lookup(CanFail, LookupInfo, !CI, CLD),
CaseNumRegLval = LookupInfo ^ stsil_case_id_reg,
InitCaseNumRegCode = singleton(
llds_instr(assign(CaseNumRegLval, const(llconst_int(-1))),
"initialize case id to invalid")
),
Info = stsi_lookup(LookupInfo),
map.to_sorted_assoc_list(KeyToCaseIdMap, StrsCaseIds),
create_nested_trie_switch(Info, VarRval, StrsCaseIds, TrieCode, !CI),
AfterLabel = LookupInfo ^ stsil_after_label,
AfterLabelCode = singleton(
llds_instr(label(AfterLabel), "after the trie search")
),
SetCaseNumCode = InitCaseNumRegCode ++ TrieCode ++ AfterLabelCode,
% We must generate the failure code in the context in which
% none of the switch arms have been executed yet.
(
CanFail = cannot_fail,
SetAndCheckCaseNumCode = SetCaseNumCode
;
CanFail = can_fail,
get_next_label(NonFailLabel, !CI),
CaseNumIsValid = binop(int_cmp(int_type_int, ge),
lval(CaseNumRegLval), const(llconst_int(0))),
TestForFailCode = singleton(
llds_instr(if_val(CaseNumIsValid, code_label(NonFailLabel)),
"branch around fail code")
),
FailCode = LookupInfo ^ stsil_fail_code,
NonFailLabelCode = singleton(
llds_instr(label(NonFailLabel), "non-fail label")
),
SetAndCheckCaseNumCode = SetCaseNumCode ++
TestForFailCode ++ FailCode ++ NonFailLabelCode
),
(
CaseConsts = all_one_soln(CaseIdToValuesMap),
map.to_assoc_list(CaseIdToValuesMap, CaseIdToValuesAL),
generate_string_trie_simple_lookup_switch(LookupInfo, CaseIdToValuesAL,
OutVars, OutTypes, EndLabel, EndBranch,
SetAndCheckCaseNumCode, Code, !MaybeEnd, !CI, CLD)
;
CaseConsts = some_several_solns(CaseIdToValuesListMap,
CaseConstsSeveralLlds),
map.to_assoc_list(CaseIdToValuesListMap, CaseIdToValuesListAL),
generate_string_trie_several_soln_lookup_switch(LookupInfo,
CaseConstsSeveralLlds, CaseIdToValuesListAL,
OutVars, OutTypes, EndLabel, EndBranch,
SetAndCheckCaseNumCode, Code, !MaybeEnd, !CI, CLD)
).
%---------------------------------------------------------------------------%
:- pred generate_string_trie_simple_lookup_switch(
string_trie_switch_info_lookup::in,
assoc_list(case_id, list(rval))::in, list(prog_var)::in,
list(llds_type)::in, label::in, end_branch_info::in,
llds_code::in, llds_code::out, branch_end::in, branch_end::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_string_trie_simple_lookup_switch(LookupInfo, CaseValues,
OutVars, OutTypes, EndLabel, EndBranch, SetAndCheckCaseNumCode, Code,
!MaybeEnd, !CI, !.CLD) :-
(
OutVars = [],
generate_single_soln_table_lookup_code_no_vars(EndBranch, LookupCode,
!MaybeEnd, !.CLD)
;
OutVars = [_ | _],
NumPrevColumns = 0,
MainRowTypes = OutTypes,
construct_string_trie_simple_lookup_vector(CaseValues, 0,
cord.init, MainTableRvalsCord),
MainTableRvals = cord.list(MainTableRvalsCord),
add_vector_static_cell(MainRowTypes, MainTableRvals,
MainTableDataId, !CI),
CaseIdRegLval = LookupInfo ^ stsil_case_id_reg,
MainRowSelect = main_row_number_reg(lval(CaseIdRegLval), OutTypes),
generate_single_soln_table_lookup_code_some_vars(MainTableDataId,
MainRowSelect, NumPrevColumns, OutVars, EndBranch, LookupCode,
!MaybeEnd, !.CI, !.CLD)
),
MainCode = SetAndCheckCaseNumCode ++ LookupCode,
SwitchKindStr = "string trie single soln lookup switch",
add_switch_kind_comment_and_end_label(SwitchKindStr, EndLabel,
MainCode, Code).
:- pred construct_string_trie_simple_lookup_vector(
assoc_list(case_id, list(rval))::in, int::in,
cord(list(rval))::in, cord(list(rval))::out) is det.
construct_string_trie_simple_lookup_vector([], _, !RvalsCord).
construct_string_trie_simple_lookup_vector([CaseValues | CasesValues], RowNum,
!RvalsCord) :-
CaseValues = CaseId - OutVarRvals,
CaseId = case_id(CaseIdNum),
expect(unify(RowNum, CaseIdNum), $pred, "RowNum != CaseIdNum"),
RowRvals = OutVarRvals,
cord.snoc(RowRvals, !RvalsCord),
construct_string_trie_simple_lookup_vector(CasesValues, RowNum + 1,
!RvalsCord).
%---------------------%
:- pred generate_string_trie_several_soln_lookup_switch(
string_trie_switch_info_lookup::in, case_consts_several_llds::in,
assoc_list(case_id, soln_consts(rval))::in,
list(prog_var)::in, list(llds_type)::in, label::in, end_branch_info::in,
llds_code::in, llds_code::out, branch_end::in, branch_end::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_string_trie_several_soln_lookup_switch(LookupInfo,
CaseConstsSeveralLlds, CaseIdToValuesListAL, OutVars, OutTypes,
EndLabel, EndBranch, SetAndCheckCaseNumCode, Code,
!MaybeEnd, !CI, !.CLD) :-
NumPrevColumns = 0,
MainRowTypes = [lt_int(int_type_int), lt_int(int_type_int) | OutTypes],
list.length(OutVars, NumOutVars),
% The later solutions table has one column for each output var,
% and no other columns.
NumLaterColumns = NumOutVars,
LaterSolnsRowNumber0 = 1,
DummyOutRvals = list.map(default_value_for_type, OutTypes),
LaterSolnsRowsCord0 = cord.singleton(DummyOutRvals),
construct_string_trie_several_soln_lookup_vector(NumLaterColumns,
CaseIdToValuesListAL, 0, cord.init, MainRvalsCord,
LaterSolnsRowNumber0, LaterSolnsRowsCord0, LaterSolnsRowsCord,
0, OneSolnCaseCount, 0, SeveralSolnsCaseCount),
MainRvals = cord.list(MainRvalsCord),
LaterSolnsRows = cord.list(LaterSolnsRowsCord),
add_vector_static_cell(MainRowTypes, MainRvals, MainTableDataId, !CI),
add_vector_static_cell(OutTypes, LaterSolnsRows, LaterTableDataId, !CI),
LaterTableAddrRval = const(llconst_data_addr(LaterTableDataId)),
CaseIdRegLval = LookupInfo ^ stsil_case_id_reg,
MainRowSelect = main_row_number_reg(lval(CaseIdRegLval), MainRowTypes),
acquire_and_setup_lookup_base_reg(MainTableDataId, EndBranch,
MainRowSelect, BaseRegLval, SetBaseRegCode, !CLD),
generate_multi_soln_table_lookup_code(CaseConstsSeveralLlds,
OneSolnCaseCount - kind_one_soln,
[SeveralSolnsCaseCount - kind_several_solns],
NumPrevColumns, OutVars, EndLabel, BaseRegLval, LaterTableAddrRval,
EndBranch, LookupResultsCode, !MaybeEnd, !CI, !.CLD),
MainCode = SetAndCheckCaseNumCode ++ SetBaseRegCode ++ LookupResultsCode,
SwitchKindStr = "string trie multi soln lookup switch",
add_switch_kind_comment_and_end_label(SwitchKindStr, EndLabel,
MainCode, Code).
:- pred construct_string_trie_several_soln_lookup_vector(int::in,
assoc_list(case_id, soln_consts(rval))::in, int::in,
cord(list(rval))::in, cord(list(rval))::out,
int::in, cord(list(rval))::in, cord(list(rval))::out,
int::in, int::out, int::in, int::out) is det.
construct_string_trie_several_soln_lookup_vector(_, [],
_, !MainRvalsCord, _, !LaterSolnsRowsCord,
!OneSolnCaseCount, !SeveralSolnsCaseCount).
construct_string_trie_several_soln_lookup_vector(NumLaterColumns,
[CaseValues | CasesValues], MainRowNum, !MainRvalsCord,
!.LaterNextRowNum, !LaterSolnsRowsCord,
!OneSolnCaseCount, !SeveralSolnsCaseCount) :-
CaseValues = CaseId - SolnConsts,
CaseId = case_id(CaseIdNum),
expect(unify(MainRowNum, CaseIdNum), $pred, "RowNum != CaseIdNum"),
(
SolnConsts = one_soln(OutVarRvals),
!:OneSolnCaseCount = !.OneSolnCaseCount + 1,
ZeroRval = const(llconst_int(0)),
% The first ZeroRval means there is exactly one solution for
% this case; the second ZeroRval is a dummy that won't be
% referenced.
MainRowRvals = [ZeroRval, ZeroRval | OutVarRvals]
;
SolnConsts = several_solns(FirstSolnRvals, LaterSolns),
!:SeveralSolnsCaseCount = !.SeveralSolnsCaseCount + 1,
list.length(LaterSolns, NumLaterSolns),
FirstRowOffset = !.LaterNextRowNum * NumLaterColumns,
LastRowOffset = (!.LaterNextRowNum + NumLaterSolns - 1) *
NumLaterColumns,
FirstRowRval = const(llconst_int(FirstRowOffset)),
LastRowRval = const(llconst_int(LastRowOffset)),
MainRowRvals = [FirstRowRval, LastRowRval | FirstSolnRvals],
!:LaterNextRowNum = !.LaterNextRowNum + NumLaterSolns,
!:LaterSolnsRowsCord =
!.LaterSolnsRowsCord ++ cord.from_list(LaterSolns)
),
cord.snoc(MainRowRvals, !MainRvalsCord),
construct_string_trie_several_soln_lookup_vector(NumLaterColumns,
CasesValues, MainRowNum + 1, !MainRvalsCord,
!.LaterNextRowNum, !LaterSolnsRowsCord,
!OneSolnCaseCount, !SeveralSolnsCaseCount).
%---------------------%
:- type string_trie_switch_info_lookup
---> string_trie_switch_info_lookup(
stsil_encoding :: string_encoding,
stsil_branch_start :: position_info,
stsil_code_unit_reg :: lval,
stsil_case_id_reg :: lval,
stsil_after_label :: label,
stsil_after_code_addr :: code_addr,
stsil_goto_after_code :: llds_code,
stsil_fail_code :: llds_code
).
:- pred init_string_trie_switch_info_lookup(can_fail::in,
string_trie_switch_info_lookup::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
init_string_trie_switch_info_lookup(CanFail, Info, !CI, !.CLD) :-
Encoding = target_string_encoding(target_c),
% See the comment about acquire_reg/release_reg in the
% init_string_binary_switch_info predicate below.
acquire_reg(reg_r, CodeUnitReg, !CLD),
acquire_reg(reg_r, CaseIdReg, !CLD),
release_reg(CodeUnitReg, !CLD),
release_reg(CaseIdReg, !CLD),
remember_position(!.CLD, BranchStart),
get_next_label(AfterLabel, !CI),
AfterCodeAddr = code_label(AfterLabel),
GotoAfterCode = singleton(
llds_instr(goto(AfterCodeAddr), "go to the code after the trie")
),
(
CanFail = cannot_fail,
FailCode = empty
;
CanFail = can_fail,
generate_failure(FailCode, !CI, !.CLD)
),
Info = string_trie_switch_info_lookup(Encoding, BranchStart,
CodeUnitReg, CaseIdReg, AfterLabel, AfterCodeAddr, GotoAfterCode,
FailCode).
%---------------------------------------------------------------------------%
:- type string_trie_switch_info
---> stsi_jump(string_trie_switch_info_jump)
; stsi_lookup(string_trie_switch_info_lookup).
%---------------------%
:- pred generate_trie_case_or_fall_through(string_trie_switch_info::in,
rval::in, case_id::in, llds_code::out, code_info::in, code_info::out)
is det.
generate_trie_case_or_fall_through(Info, CondRval, CaseId, CaseCode, !CI) :-
(
Info = stsi_jump(JumpInfo),
JumpInfo = string_trie_switch_info_jump(_Encoding, CaseIdToLabelMap,
_CodeUnitRegLval, _BranchStart, _FailLabel, _FailCode,
_GotoFailCode),
map.lookup(CaseIdToLabelMap, CaseId, CaseLabel),
CaseCodeAddr = code_label(CaseLabel),
CaseCode = singleton(
llds_instr(if_val(CondRval, CaseCodeAddr), "if match; goto case")
)
% If the test fails, fall through
;
Info = stsi_lookup(LookupInfo),
get_next_label(FallThroughLabel, !CI),
CaseIdRegLval = LookupInfo ^ stsil_case_id_reg,
AfterCodeAddr = LookupInfo ^ stsil_after_code_addr,
negate_rval(CondRval, NegCondRval),
CaseId = case_id(CaseIdNum),
CaseIdRval = const(llconst_int(CaseIdNum)),
CaseCode = from_list([
llds_instr(if_val(NegCondRval, code_label(FallThroughLabel)),
"if not match; fall through"),
llds_instr(assign(CaseIdRegLval, CaseIdRval), "assign CaseId"),
llds_instr(goto(AfterCodeAddr), "match; goto end"),
llds_instr(label(FallThroughLabel), "fall through label")
])
).
:- pred generate_trie_goto_fail_code(string_trie_switch_info::in,
llds_code::out) is det.
generate_trie_goto_fail_code(Info, GotoFailCode) :-
(
Info = stsi_jump(JumpInfo),
GotoFailCode = JumpInfo ^ stsij_goto_fail_code
;
Info = stsi_lookup(LookupInfo),
AfterCodeAddr = LookupInfo ^ stsil_after_code_addr,
GotoFailCode = singleton(
llds_instr(goto(AfterCodeAddr),
"no match; goto after with no case id set")
)
).
:- pred get_encoding(string_trie_switch_info::in, string_encoding::out) is det.
get_encoding(Info, Encoding) :-
(
Info = stsi_jump(JumpInfo),
Encoding = JumpInfo ^ stsij_encoding
;
Info = stsi_lookup(LookupInfo),
Encoding = LookupInfo ^ stsil_encoding
).
:- pred get_code_unit_reg(string_trie_switch_info::in, lval::out) is det.
get_code_unit_reg(Info, CodeUnitReg) :-
(
Info = stsi_jump(JumpInfo),
CodeUnitReg = JumpInfo ^ stsij_code_unit_reg
;
Info = stsi_lookup(LookupInfo),
CodeUnitReg = LookupInfo ^ stsil_code_unit_reg
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
generate_string_hash_jump_switch(VarRval, VarName, TaggedCases,
CodeModel, CanFail, SwitchGoalInfo, EndLabel, MaybeEnd, Code,
!CI, CLD) :-
init_string_hash_switch_info(CanFail, HashSwitchInfo, !CI, CLD),
BranchStart = HashSwitchInfo ^ shsi_branch_start,
Params = represent_params(VarName, SwitchGoalInfo, CodeModel, BranchStart,
EndLabel),
% Generate code for the cases, and remember the label of each case.
map.init(CaseLabelMap0),
represent_tagged_cases_in_string_hash_switch(Params, TaggedCases,
[], StrsLabels, CaseLabelMap0, CaseLabelMap, no, MaybeEnd, !CI),
% Compute the hash table.
construct_string_hash_cases(StrsLabels, allow_doubling,
TableSize, HashSlotsMap, HashOp, NumCollisions),
HashMask = TableSize - 1,
% Generate the data structures for the hash table.
FailLabel = HashSwitchInfo ^ shsi_fail_label,
construct_string_hash_jump_vectors(0, TableSize, HashSlotsMap, FailLabel,
NumCollisions, cord.init, MainTableRvalsCord,
cord.init, MaybeTargetsCord),
MainTableRvals = cord.list(MainTableRvalsCord),
MaybeTargets = cord.list(MaybeTargetsCord),
% Generate the code for the hash table lookup.
( if NumCollisions = 0 then
NumColumns = 1,
MainRowTypes = [lt_string],
ArrayElemTypes = [scalar_elem_string]
else
NumColumns = 2,
MainRowTypes = [lt_string, lt_int(int_type_int)],
ArrayElemTypes = [scalar_elem_string, scalar_elem_int]
),
add_vector_static_cell(MainRowTypes, MainTableRvals, MainTableDataId, !CI),
MainTableAddrRval = const(llconst_data_addr(MainTableDataId)),
ArrayElemType = array_elem_struct(ArrayElemTypes),
SlotReg = HashSwitchInfo ^ shsi_slot_reg,
MaxIndex = list.length(MaybeTargets) - 1,
MatchCode = from_list([
% See the comment at the top of the module about why we use
% a computed_goto here.
llds_instr(computed_goto(lval(SlotReg), yes(MaxIndex), MaybeTargets),
"jump to the corresponding code")
]),
% Generate the code for the cases, and put it all together.
add_not_yet_included_cases(CasesCode, CaseLabelMap, _),
generate_string_hash_switch_search(HashSwitchInfo, VarRval,
MainTableAddrRval, ArrayElemType, NumColumns, HashOp, HashMask,
NumCollisions, EndLabel, "jump", CasesCode, MatchCode, Code).
:- pred construct_string_hash_jump_vectors(int::in, int::in,
map(int, string_hash_slot(label))::in, label::in, int::in,
cord(list(rval))::in, cord(list(rval))::out,
cord(maybe(label))::in, cord(maybe(label))::out) is det.
construct_string_hash_jump_vectors(Slot, TableSize, HashSlotMap, FailLabel,
NumCollisions, !TableRvalsCord, !MaybeTargetsCord) :-
( if Slot = TableSize then
true
else
( if map.search(HashSlotMap, Slot, SlotInfo) then
SlotInfo = string_hash_slot(String, Next, CaseLabel),
NextSlotRval = const(llconst_int(Next)),
StringRval = const(llconst_string(String)),
Target = CaseLabel
else
StringRval = const(llconst_int(0)),
NextSlotRval = const(llconst_int(-2)),
Target = FailLabel
),
( if NumCollisions = 0 then
TableRowRvals = [StringRval]
else
TableRowRvals = [StringRval, NextSlotRval]
),
cord.snoc(TableRowRvals, !TableRvalsCord),
cord.snoc(yes(Target), !MaybeTargetsCord),
construct_string_hash_jump_vectors(Slot + 1, TableSize, HashSlotMap,
FailLabel, NumCollisions, !TableRvalsCord, !MaybeTargetsCord)
).
:- pred represent_tagged_cases_in_string_hash_switch(represent_params::in,
list(tagged_case)::in,
assoc_list(string, label)::in, assoc_list(string, label)::out,
case_label_map::in, case_label_map::out,
branch_end::in, branch_end::out, code_info::in, code_info::out) is det.
represent_tagged_cases_in_string_hash_switch(_, [],
!StrsLabels, !CaseLabelMap, !MaybeEnd, !CI).
represent_tagged_cases_in_string_hash_switch(Params,
[TaggedCase | TaggedCases],
!StrsLabels, !CaseLabelMap, !MaybeEnd, !CI) :-
represent_tagged_case_for_llds(Params, TaggedCase, Label,
!CaseLabelMap, !MaybeEnd, !CI, unit, _),
TaggedCase = tagged_case(MainTaggedConsId, OtherTaggedConsIds, _, _),
record_label_for_string(Label, MainTaggedConsId, !StrsLabels),
list.foldl(record_label_for_string(Label), OtherTaggedConsIds,
!StrsLabels),
represent_tagged_cases_in_string_hash_switch(Params, TaggedCases,
!StrsLabels, !CaseLabelMap, !MaybeEnd, !CI).
:- pred record_label_for_string(label::in, tagged_cons_id::in,
assoc_list(string, label)::in, assoc_list(string, label)::out) is det.
record_label_for_string(Label, TaggedConsId, !StrsLabels) :-
TaggedConsId = tagged_cons_id(_ConsId, Tag),
( if Tag = string_tag(String) then
!:StrsLabels = [String - Label | !.StrsLabels]
else
unexpected($pred, "non-string tag")
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
generate_string_hash_lookup_switch(VarRval, LookupSwitchInfo,
CanFail, EndLabel, !:MaybeEnd, Code, !:CI) :-
LookupSwitchInfo = lookup_switch_info(KeyToCaseIdMap, CaseConsts,
OutVars, OutTypes, EndBranch, !:MaybeEnd, !:CI, CLD),
(
CaseConsts = all_one_soln(CaseIdToValuesMap),
compose_maps(KeyToCaseIdMap, CaseIdToValuesMap, KeyToValuesMap),
map.to_assoc_list(KeyToValuesMap, KeyValuesAL),
generate_string_hash_simple_lookup_switch(VarRval, KeyValuesAL,
OutVars, OutTypes, CanFail, EndLabel, EndBranch, Code,
!MaybeEnd, !CI, CLD)
;
CaseConsts = some_several_solns(CaseIdToSolnsMap,
CaseConstsSeveralLlds),
compose_maps(KeyToCaseIdMap, CaseIdToSolnsMap, KeyToSolnsMap),
map.to_assoc_list(KeyToSolnsMap, KeySolnsAL),
generate_string_hash_several_soln_lookup_switch(CaseConstsSeveralLlds,
VarRval, KeySolnsAL, OutVars, OutTypes, CanFail, EndLabel,
EndBranch, Code, !MaybeEnd, !CI, CLD)
).
%---------------------------------------------------------------------------%
:- pred generate_string_hash_simple_lookup_switch(rval::in,
assoc_list(string, list(rval))::in, list(prog_var)::in,
list(llds_type)::in, can_fail::in, label::in, end_branch_info::in,
llds_code::out, branch_end::in, branch_end::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_string_hash_simple_lookup_switch(VarRval, CaseValues,
OutVars, OutTypes, CanFail, EndLabel, EndBranch, Code,
!MaybeEnd, !CI, !.CLD) :-
% This predicate, generate_string_hash_several_soln_lookup_switch,
% and generate_string_hash_lookup_switch do similar tasks using
% similar code, so if you need to update one, you probably need to
% update them all.
init_string_hash_switch_info(CanFail, HashSwitchInfo, !CI, !.CLD),
% Compute the hash table.
construct_string_hash_cases(CaseValues, allow_doubling, TableSize,
HashSlotsMap, HashOp, NumCollisions),
HashMask = TableSize - 1,
list.length(OutVars, NumOutVars),
% For the LLDS backend, array indexing ops don't need the element types,
% so it is ok to lie for OutElemTypes.
list.duplicate(NumOutVars, scalar_elem_generic, OutElemTypes),
DummyOutRvals = list.map(default_value_for_type, OutTypes),
( if NumCollisions = 0 then
NumPrevColumns = 1,
NumColumns = 1 + NumOutVars,
ArrayElemTypes = [scalar_elem_string | OutElemTypes],
MainRowTypes = [lt_string | OutTypes]
else
NumPrevColumns = 2,
NumColumns = 2 + NumOutVars,
ArrayElemTypes = [scalar_elem_string, scalar_elem_int | OutElemTypes],
MainRowTypes = [lt_string, lt_int(int_type_int) | OutTypes]
),
ArrayElemType = array_elem_struct(ArrayElemTypes),
% Generate the static lookup table for this switch.
construct_string_hash_simple_lookup_vector(0, TableSize, HashSlotsMap,
NumCollisions, DummyOutRvals, cord.init, MainTableRvalsCord),
MainTableRvals = cord.list(MainTableRvalsCord),
add_vector_static_cell(MainRowTypes, MainTableRvals,
MainTableDataId, !CI),
MainTableAddrRval = const(llconst_data_addr(MainTableDataId)),
(
OutVars = [],
generate_single_soln_table_lookup_code_no_vars(EndBranch, LookupCode,
!MaybeEnd, !.CLD)
;
OutVars = [_ | _],
RowStartRegLval = HashSwitchInfo ^ shsi_row_start_reg,
MainRowSelect = main_row_start_offset_reg(lval(RowStartRegLval)),
generate_single_soln_table_lookup_code_some_vars(MainTableDataId,
MainRowSelect, NumPrevColumns, OutVars, EndBranch, LookupCode,
!MaybeEnd, !.CI, !.CLD)
),
append_goto_end(EndLabel, LookupCode, LookupGotoEndCode),
generate_string_hash_switch_search(HashSwitchInfo, VarRval,
MainTableAddrRval, ArrayElemType, NumColumns, HashOp, HashMask,
NumCollisions, EndLabel, "single soln lookup",
empty, LookupGotoEndCode, Code).
:- pred construct_string_hash_simple_lookup_vector(int::in, int::in,
map(int, string_hash_slot(list(rval)))::in, int::in, list(rval)::in,
cord(list(rval))::in, cord(list(rval))::out) is det.
construct_string_hash_simple_lookup_vector(Slot, TableSize, HashSlotMap,
NumCollisions, DummyOutRvals, !RvalsCord) :-
( if Slot = TableSize then
true
else
( if map.search(HashSlotMap, Slot, SlotInfo) then
SlotInfo = string_hash_slot(String, Next, OutVarRvals),
NextSlotRval = const(llconst_int(Next)),
StringRval = const(llconst_string(String))
else
StringRval = const(llconst_int(0)),
NextSlotRval = const(llconst_int(-2)),
OutVarRvals = DummyOutRvals
),
( if NumCollisions = 0 then
RowRvals = [StringRval | OutVarRvals]
else
RowRvals = [StringRval, NextSlotRval | OutVarRvals]
),
cord.snoc(RowRvals, !RvalsCord),
construct_string_hash_simple_lookup_vector(Slot + 1, TableSize,
HashSlotMap, NumCollisions, DummyOutRvals, !RvalsCord)
).
%---------------------------------------------------------------------------%
:- pred generate_string_hash_several_soln_lookup_switch(
case_consts_several_llds::in, rval::in,
assoc_list(string, soln_consts(rval))::in,
list(prog_var)::in, list(llds_type)::in,
can_fail::in, label::in, end_branch_info::in,
llds_code::out, branch_end::in, branch_end::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_string_hash_several_soln_lookup_switch(CaseConstsSeveralLlds,
VarRval, CaseSolns, OutVars, OutTypes, CanFail, EndLabel, EndBranch,
Code, !MaybeEnd, !CI, !.CLD) :-
% This predicate, generate_string_hash_simple_lookup_switch,
% and generate_string_hash_lookup_switch do similar tasks using
% similar code, so if you need to update one, you probably need to
% update them all.
init_string_hash_switch_info(CanFail, HashSwitchInfo, !CI, !.CLD),
% Compute the hash table.
construct_string_hash_cases(CaseSolns, allow_doubling, TableSize,
HashSlotsMap, HashOp, NumCollisions),
HashMask = TableSize - 1,
list.length(OutVars, NumOutVars),
% For the LLDS backend, array indexing ops don't need the element types,
% so it is ok to lie for OutElemTypes.
list.duplicate(NumOutVars, scalar_elem_generic, OutElemTypes),
( if NumCollisions = 0 then
NumColumns = 3 + NumOutVars,
NumPrevColumns = 1,
ArrayElemTypes = [scalar_elem_string,
scalar_elem_int, scalar_elem_int | OutElemTypes],
MainRowTypes = [lt_string, lt_int(int_type_int),
lt_int(int_type_int) | OutTypes]
else
NumColumns = 4 + NumOutVars,
NumPrevColumns = 2,
ArrayElemTypes = [scalar_elem_string, scalar_elem_int,
scalar_elem_int, scalar_elem_int | OutElemTypes],
MainRowTypes = [lt_string, lt_int(int_type_int), lt_int(int_type_int),
lt_int(int_type_int) | OutTypes]
),
ArrayElemType = array_elem_struct(ArrayElemTypes),
% Generate the static lookup table for this switch.
InitLaterSolnRowNumber = 1,
DummyOutRvals = list.map(default_value_for_type, OutTypes),
LaterSolnsRowsCord0 = singleton(DummyOutRvals),
construct_string_hash_several_soln_lookup_vector(0, TableSize,
HashSlotsMap, DummyOutRvals, NumOutVars, NumCollisions,
cord.init, MainTableRvalsCord, InitLaterSolnRowNumber,
LaterSolnsRowsCord0, LaterSolnsRowsCord,
0, OneSolnCaseCount, 0, SeveralSolnsCaseCount),
MainTableRvals = cord.list(MainTableRvalsCord),
LaterSolnsRows = cord.list(LaterSolnsRowsCord),
add_vector_static_cell(MainRowTypes, MainTableRvals, MainTableDataId, !CI),
add_vector_static_cell(OutTypes, LaterSolnsRows, LaterTableDataId, !CI),
MainTableAddrRval = const(llconst_data_addr(MainTableDataId)),
LaterTableAddrRval = const(llconst_data_addr(LaterTableDataId)),
RowStartRegLval = HashSwitchInfo ^ shsi_row_start_reg,
MainRowSelect = main_row_start_offset_reg(lval(RowStartRegLval)),
acquire_and_setup_lookup_base_reg(MainTableDataId, EndBranch,
MainRowSelect, BaseRegLval, SetBaseRegCode, !CLD),
generate_multi_soln_table_lookup_code(CaseConstsSeveralLlds,
OneSolnCaseCount - kind_one_soln,
[SeveralSolnsCaseCount - kind_several_solns],
NumPrevColumns, OutVars, EndLabel, BaseRegLval, LaterTableAddrRval,
EndBranch, LookupResultsCode, !MaybeEnd, !CI, !.CLD),
MatchCode = SetBaseRegCode ++ LookupResultsCode,
generate_string_hash_switch_search(HashSwitchInfo, VarRval,
MainTableAddrRval, ArrayElemType, NumColumns, HashOp, HashMask,
NumCollisions, EndLabel, "multi soln lookup", empty, MatchCode, Code).
:- pred construct_string_hash_several_soln_lookup_vector(int::in, int::in,
map(int, string_hash_slot(soln_consts(rval)))::in, list(rval)::in,
int::in, int::in, cord(list(rval))::in, cord(list(rval))::out,
int::in, cord(list(rval))::in, cord(list(rval))::out,
int::in, int::out, int::in, int::out) is det.
construct_string_hash_several_soln_lookup_vector(Slot, TableSize, HashSlotMap,
DummyOutRvals, NumOutVars, NumCollisions,
!MainRvalsCord, !.LaterNextRow, !LaterSolnRowsCord,
!OneSolnCaseCount, !SeveralSolnsCaseCount) :-
( if Slot = TableSize then
true
else
( if map.search(HashSlotMap, Slot, SlotInfo) then
SlotInfo = string_hash_slot(String, Next, Soln),
StringRval = const(llconst_string(String)),
NextSlotRval = const(llconst_int(Next)),
(
Soln = one_soln(OutVarRvals),
!:OneSolnCaseCount = !.OneSolnCaseCount + 1,
ZeroRval = const(llconst_int(0)),
% The first ZeroRval means there is exactly one solution for
% this case; the second ZeroRval is a dummy that won't be
% referenced.
MainRowTail = [ZeroRval, ZeroRval | OutVarRvals],
( if NumCollisions = 0 then
MainRowRvals = [StringRval | MainRowTail]
else
MainRowRvals = [StringRval, NextSlotRval | MainRowTail]
)
;
Soln = several_solns(FirstSolnRvals, LaterSolns),
!:SeveralSolnsCaseCount = !.SeveralSolnsCaseCount + 1,
list.length(LaterSolns, NumLaterSolns),
FirstRowOffset = !.LaterNextRow * NumOutVars,
LastRowOffset = (!.LaterNextRow + NumLaterSolns - 1)
* NumOutVars,
FirstRowRval = const(llconst_int(FirstRowOffset)),
LastRowRval = const(llconst_int(LastRowOffset)),
MainRowTail = [FirstRowRval, LastRowRval | FirstSolnRvals],
( if NumCollisions = 0 then
MainRowRvals = [StringRval | MainRowTail]
else
MainRowRvals = [StringRval, NextSlotRval | MainRowTail]
),
!:LaterNextRow = !.LaterNextRow + NumLaterSolns,
!:LaterSolnRowsCord =
!.LaterSolnRowsCord ++ from_list(LaterSolns)
)
else
% The zero in the StringRval slot means that this bucket is empty.
StringRval = const(llconst_int(0)),
NextSlotRval = const(llconst_int(-2)),
ZeroRval = const(llconst_int(0)),
MainRowTail = [ZeroRval, ZeroRval | DummyOutRvals],
( if NumCollisions = 0 then
MainRowRvals = [StringRval | MainRowTail]
else
MainRowRvals = [StringRval, NextSlotRval | MainRowTail]
)
),
cord.snoc(MainRowRvals, !MainRvalsCord),
construct_string_hash_several_soln_lookup_vector(Slot + 1, TableSize,
HashSlotMap, DummyOutRvals, NumOutVars, NumCollisions,
!MainRvalsCord, !.LaterNextRow, !LaterSolnRowsCord,
!OneSolnCaseCount, !SeveralSolnsCaseCount)
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- type string_hash_switch_info
---> string_hash_switch_info(
shsi_slot_reg :: lval,
shsi_row_start_reg :: lval,
shsi_string_reg :: lval,
shsi_loop_start_label :: label,
shsi_no_match_label :: label,
shsi_fail_label :: label,
shsi_branch_start :: position_info,
shsi_fail_code :: llds_code
).
:- pred init_string_hash_switch_info(can_fail::in,
string_hash_switch_info::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
init_string_hash_switch_info(CanFail, Info, !CI, !.CLD) :-
% See the comment about acquire_reg/release_reg in the
% init_string_binary_switch_info predicate below.
acquire_reg(reg_r, SlotReg, !CLD),
acquire_reg(reg_r, RowStartReg, !CLD),
acquire_reg(reg_r, StringReg, !CLD),
release_reg(SlotReg, !CLD),
release_reg(RowStartReg, !CLD),
release_reg(StringReg, !CLD),
get_next_label(LoopStartLabel, !CI),
get_next_label(FailLabel, !CI),
get_next_label(NoMatchLabel, !CI),
% We must generate the failure code in the context in which
% none of the switch arms have been executed yet.
remember_position(!.CLD, BranchStart),
generate_string_switch_fail(CanFail, FailCode, !CI, !.CLD),
Info = string_hash_switch_info(SlotReg, RowStartReg, StringReg,
LoopStartLabel, NoMatchLabel, FailLabel, BranchStart, FailCode).
:- pred generate_string_hash_switch_search(string_hash_switch_info::in,
rval::in, rval::in, array_elem_type::in, int::in, unary_op::in, int::in,
int::in, label::in, string::in, llds_code::in, llds_code::in,
llds_code::out) is det.
generate_string_hash_switch_search(Info, VarRval, TableAddrRval,
ArrayElemType, NumColumns, HashOp, HashMask, NumCollisions,
EndLabel, KindStr, CasesCode, MatchCode, Code) :-
SlotReg = Info ^ shsi_slot_reg,
RowStartReg = Info ^ shsi_row_start_reg,
StringReg = Info ^ shsi_string_reg,
LoopStartLabel = Info ^ shsi_loop_start_label,
NoMatchLabel = Info ^ shsi_no_match_label,
FailLabel = Info ^ shsi_fail_label,
FailCode = Info ^ shsi_fail_code,
( if NumCollisions = 0 then
( if NumColumns = 1 then
BaseReg = SlotReg,
MultiplyInstrs = []
else
BaseReg = RowStartReg,
MultiplyInstrs = [
llds_instr(
assign(RowStartReg,
binop(int_arith(int_type_int, ao_mul),
lval(SlotReg), const(llconst_int(NumColumns)))),
"find the start of the row")
]
),
HashSearchCode =
from_list([
llds_instr(
assign(SlotReg,
binop(bitwise_and(int_type_int),
unop(HashOp, VarRval),
const(llconst_int(HashMask)))),
"compute the hash value of the input string") |
MultiplyInstrs]) ++
from_list([
llds_instr(
assign(StringReg,
binop(array_index(ArrayElemType),
TableAddrRval, lval(BaseReg))),
"lookup the string for this hash slot"),
llds_instr(
if_val(
binop(logical_or,
binop(int_cmp(int_type_int, eq),
lval(StringReg), const(llconst_int(0))),
binop(str_cmp(ne), lval(StringReg), VarRval)),
code_label(FailLabel)),
"did we find a match? nofulljump")
]) ++
MatchCode ++
from_list([
llds_instr(label(FailLabel),
"handle the failure of the table search")
])
else
HashSearchCode =
from_list([
llds_instr(
assign(SlotReg,
binop(bitwise_and(int_type_int),
unop(HashOp, VarRval),
const(llconst_int(HashMask)))),
"compute the hash value of the input string"),
llds_instr(label(LoopStartLabel),
"begin hash chain loop, nofulljump"),
llds_instr(
assign(RowStartReg,
binop(int_arith(int_type_int, ao_mul),
lval(SlotReg), const(llconst_int(NumColumns)))),
"find the start of the row"),
llds_instr(
assign(StringReg,
binop(array_index(ArrayElemType),
TableAddrRval, lval(RowStartReg))),
"lookup the string for this hash slot"),
llds_instr(
if_val(
binop(logical_or,
binop(int_cmp(int_type_int, eq),
lval(StringReg), const(llconst_int(0))),
binop(str_cmp(ne), lval(StringReg), VarRval)),
code_label(NoMatchLabel)),
"did we find a match? nofulljump")
]) ++
MatchCode ++
from_list([
llds_instr(label(NoMatchLabel),
"no match yet, nofulljump"),
llds_instr(
assign(SlotReg,
binop(array_index(ArrayElemType),
TableAddrRval,
binop(int_arith(int_type_int, ao_add),
lval(RowStartReg), const(llconst_int(1))))),
"get next slot in hash chain"),
llds_instr(
if_val(
binop(int_cmp(int_type_int, ge),
lval(SlotReg), const(llconst_int(0))),
code_label(LoopStartLabel)),
"if not at the end of the chain, keep searching"),
llds_instr(label(FailLabel),
"handle the failure of the table search")
])
),
MainCode = HashSearchCode ++ FailCode ++ CasesCode,
string.format("string hash %s switch", [s(KindStr)], SwitchKindStr),
add_switch_kind_comment_and_end_label(SwitchKindStr, EndLabel,
MainCode, Code).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
generate_string_binary_jump_switch(VarRval, VarName, TaggedCases,
CodeModel, CanFail, SwitchGoalInfo, EndLabel, MaybeEnd, Code,
!CI, CLD) :-
init_string_binary_switch_info(CanFail, BinarySwitchInfo, !CI, CLD),
BranchStart = BinarySwitchInfo ^ sbsi_branch_start,
Params = represent_params(VarName, SwitchGoalInfo, CodeModel, BranchStart,
EndLabel),
% Compute and generate the binary search table.
map.init(CaseLabelMap0),
string_binary_cases(TaggedCases, represent_tagged_case_for_llds(Params),
CaseLabelMap0, CaseLabelMap, no, MaybeEnd, !CI, unit, _, SortedTable),
gen_string_binary_jump_slots(SortedTable,
cord.init, MainTableRvalsCord, cord.init, TargetsCord,
0, TableSize),
MainTableRvals = cord.list(MainTableRvalsCord),
Targets = cord.list(TargetsCord),
NumColumns = 2,
MainRowTypes = [lt_string, lt_int(int_type_int)],
add_vector_static_cell(MainRowTypes, MainTableRvals, TableAddr, !CI),
ArrayElemTypes = [scalar_elem_string, scalar_elem_int],
ArrayElemType = array_elem_struct(ArrayElemTypes),
TableAddrRval = const(llconst_data_addr(TableAddr)),
generate_string_binary_switch_search(BinarySwitchInfo,
VarRval, TableAddrRval, ArrayElemType, TableSize, NumColumns,
BinarySearchCode),
MidReg = BinarySwitchInfo ^ sbsi_mid_reg,
% See the comment at the top about why we use a computed_goto here.
ComputedGotoCode = singleton(
llds_instr(
computed_goto(
binop(array_index(ArrayElemType),
TableAddrRval,
binop(int_arith(int_type_int, ao_add),
binop(int_arith(int_type_int, ao_mul),
lval(MidReg),
const(llconst_int(NumColumns))),
const(llconst_int(1)))),
yes(TableSize),
Targets),
"jump to the matching case")
),
% Generate the code for the cases, and put it all together.
add_not_yet_included_cases(CasesCode, CaseLabelMap, _),
MainCode = BinarySearchCode ++ ComputedGotoCode ++ CasesCode,
SwitchKindStr = "string binary jump switch",
add_switch_kind_comment_and_end_label(SwitchKindStr, EndLabel,
MainCode, Code).
:- pred gen_string_binary_jump_slots(assoc_list(string, label)::in,
cord(list(rval))::in, cord(list(rval))::out,
cord(maybe(label))::in, cord(maybe(label))::out,
int::in, int::out) is det.
gen_string_binary_jump_slots([],
!TableRvalsCord, !MaybeTargetsCord, !CurIndex).
gen_string_binary_jump_slots([Str - Label | StrLabels],
!TableRvalsCord, !MaybeTargetsCord, !CurIndex) :-
Row = [const(llconst_string(Str)), const(llconst_int(!.CurIndex))],
cord.snoc(Row, !TableRvalsCord),
cord.snoc(yes(Label), !MaybeTargetsCord),
!:CurIndex = !.CurIndex + 1,
gen_string_binary_jump_slots(StrLabels,
!TableRvalsCord, !MaybeTargetsCord, !CurIndex).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
generate_string_binary_lookup_switch(VarRval, LookupSwitchInfo,
CanFail, EndLabel, !:MaybeEnd, Code, !:CI) :-
LookupSwitchInfo = lookup_switch_info(KeyToCaseIdMap, CaseConsts,
OutVars, OutTypes, EndBranch, !:MaybeEnd, !:CI, CLD),
(
CaseConsts = all_one_soln(CaseIdToValuesMap),
compose_maps(KeyToCaseIdMap, CaseIdToValuesMap, KeyToValuesMap),
map.to_assoc_list(KeyToValuesMap, KeyValuesAL),
generate_string_binary_simple_lookup_switch(VarRval, KeyValuesAL,
OutVars, OutTypes, CanFail, EndLabel, EndBranch, Code,
!MaybeEnd, !CI, CLD)
;
CaseConsts = some_several_solns(CaseIdToSolnsMap,
CaseConstsSeveralLlds),
compose_maps(KeyToCaseIdMap, CaseIdToSolnsMap, KeyToSolnsMap),
map.to_assoc_list(KeyToSolnsMap, KeySolnsAL),
generate_string_binary_several_soln_lookup_switch(
CaseConstsSeveralLlds, VarRval, KeySolnsAL, OutVars, OutTypes,
CanFail, EndLabel, EndBranch, Code, !MaybeEnd, !CI, CLD)
).
%---------------------------------------------------------------------------%
:- pred generate_string_binary_simple_lookup_switch(rval::in,
assoc_list(string, list(rval))::in, list(prog_var)::in,
list(llds_type)::in, can_fail::in, label::in, end_branch_info::in,
llds_code::out, branch_end::in, branch_end::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_string_binary_simple_lookup_switch(VarRval, CaseValues,
OutVars, OutTypes, CanFail, EndLabel, EndBranch, Code,
!MaybeEnd, !CI, !.CLD) :-
% This predicate, generate_string_binary_several_soln_lookup_switch,
% and generate_string_binary_lookup_switch do similar tasks using
% similar code, so if you need to update one, you probably need to
% update them all.
init_string_binary_switch_info(CanFail, BinarySwitchInfo, !CI, !.CLD),
list.length(CaseValues, TableSize),
list.length(OutVars, NumOutVars),
% For the LLDS backend, array indexing ops don't need the element types,
% so it is ok to lie here.
list.duplicate(NumOutVars, scalar_elem_generic, OutElemTypes),
ArrayElemTypes = [scalar_elem_string | OutElemTypes],
ArrayElemType = array_elem_struct(ArrayElemTypes),
NumPrevColumns = 1,
NumColumns = NumPrevColumns + NumOutVars,
% Generate the static lookup table for this switch.
construct_string_binary_simple_lookup_vector(CaseValues,
cord.init, MainTableRvalsCord),
MainTableRvals = cord.list(MainTableRvalsCord),
MainRowTypes = [lt_string | OutTypes],
add_vector_static_cell(MainRowTypes, MainTableRvals, MainTableDataId, !CI),
MainTableAddrRval = const(llconst_data_addr(MainTableDataId)),
generate_string_binary_switch_search(BinarySwitchInfo, VarRval,
MainTableAddrRval, ArrayElemType, TableSize, NumColumns,
BinarySearchCode),
(
OutVars = [],
generate_single_soln_table_lookup_code_no_vars(EndBranch, LookupCode,
!MaybeEnd, !.CLD)
;
OutVars = [_ | _],
MidRegLval = BinarySwitchInfo ^ sbsi_mid_reg,
MainRowSelect = main_row_number_reg(lval(MidRegLval), MainRowTypes),
generate_single_soln_table_lookup_code_some_vars(MainTableDataId,
MainRowSelect, NumPrevColumns, OutVars, EndBranch,
LookupCode, !MaybeEnd, !.CI, !.CLD)
),
MainCode = BinarySearchCode ++ LookupCode,
SwitchKindStr = "string binary single soln lookup switch",
add_switch_kind_comment_and_end_label(SwitchKindStr, EndLabel,
MainCode, Code).
:- pred construct_string_binary_simple_lookup_vector(
assoc_list(string, list(rval))::in,
cord(list(rval))::in, cord(list(rval))::out) is det.
construct_string_binary_simple_lookup_vector([], !RvalsCord).
construct_string_binary_simple_lookup_vector([Str - OutRvals | StrsOutRvals],
!RvalsCord) :-
RowRvals = [const(llconst_string(Str)) | OutRvals],
cord.snoc(RowRvals, !RvalsCord),
construct_string_binary_simple_lookup_vector(StrsOutRvals, !RvalsCord).
%---------------------------------------------------------------------------%
:- pred generate_string_binary_several_soln_lookup_switch(
case_consts_several_llds::in, rval::in,
assoc_list(string, soln_consts(rval))::in,
list(prog_var)::in, list(llds_type)::in,
can_fail::in, label::in, end_branch_info::in,
llds_code::out, branch_end::in, branch_end::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_string_binary_several_soln_lookup_switch(CaseConstsSeveralLlds,
VarRval, CaseSolns, OutVars, OutTypes,
CanFail, EndLabel, EndBranch, Code, !MaybeEnd, !CI, !.CLD) :-
% This predicate, generate_string_binary_simple_lookup_switch,
% and generate_string_binary_lookup_switch do similar tasks using
% similar code, so if you need to update one, you probably need to
% update them all.
init_string_binary_switch_info(CanFail, BinarySwitchInfo, !CI, !.CLD),
list.length(CaseSolns, MainTableSize),
list.length(OutVars, NumOutVars),
% For the LLDS backend, array indexing ops don't need the element types,
% so it is ok to lie here.
list.duplicate(NumOutVars, scalar_elem_generic, OutElemTypes),
ArrayElemTypes =
[scalar_elem_string, scalar_elem_int, scalar_elem_int | OutElemTypes],
ArrayElemType = array_elem_struct(ArrayElemTypes),
% Now generate the static cells into which we do the lookups of the values
% of the output variables, if there are any.
%
% We put a dummy row at the start of the later solns table, so that
% a zero in the "later solns start row" column of the main table can mean
% "no later solutions".
InitLaterSolnRowNumber = 1,
DummyLaterSolnRow = list.map(default_value_for_type, OutTypes),
LaterSolnArrayCord0 = singleton(DummyLaterSolnRow),
construct_string_binary_several_soln_lookup_vector(CaseSolns,
NumOutVars, cord.init, MainRvalsCord,
InitLaterSolnRowNumber, LaterSolnArrayCord0, LaterSolnArrayCord,
0, OneSolnCaseCount, 0, SeveralSolnsCaseCount),
MainRvals = cord.list(MainRvalsCord),
LaterSolnArray = cord.list(LaterSolnArrayCord),
MainRowTypes =
[lt_string, lt_int(int_type_int), lt_int(int_type_int) | OutTypes],
list.length(MainRowTypes, MainNumColumns),
add_vector_static_cell(MainRowTypes, MainRvals, MainTableDataId, !CI),
MainTableAddrRval = const(llconst_data_addr(MainTableDataId)),
add_vector_static_cell(OutTypes, LaterSolnArray, LaterTableDataId, !CI),
LaterTableAddrRval = const(llconst_data_addr(LaterTableDataId)),
generate_string_binary_switch_search(BinarySwitchInfo, VarRval,
MainTableAddrRval, ArrayElemType, MainTableSize, MainNumColumns,
BinarySearchCode),
MidRegLval = BinarySwitchInfo ^ sbsi_mid_reg,
MainRowSelect = main_row_number_reg(lval(MidRegLval), MainRowTypes),
acquire_and_setup_lookup_base_reg(MainTableDataId, EndBranch,
MainRowSelect, BaseRegLval, SetBaseRegCode, !CLD),
NumPrevColumns = 1,
generate_multi_soln_table_lookup_code(CaseConstsSeveralLlds,
OneSolnCaseCount - kind_one_soln,
[SeveralSolnsCaseCount - kind_several_solns],
NumPrevColumns, OutVars, EndLabel, BaseRegLval, LaterTableAddrRval,
EndBranch, LookupResultsCode, !MaybeEnd, !CI, !.CLD),
MainCode = BinarySearchCode ++ SetBaseRegCode ++ LookupResultsCode,
SwitchKindStr = "string binary multi soln lookup switch",
add_switch_kind_comment_and_end_label(SwitchKindStr, EndLabel,
MainCode, Code).
:- pred construct_string_binary_several_soln_lookup_vector(
assoc_list(string, soln_consts(rval))::in, int::in,
cord(list(rval))::in, cord(list(rval))::out,
int::in, cord(list(rval))::in, cord(list(rval))::out,
int::in, int::out, int::in, int::out) is det.
construct_string_binary_several_soln_lookup_vector([],
_NumOutVars, !MainRvalsCord, _LaterNextRow, !LaterSolnArray,
!OneSolnCaseCount, !SeveralSolnCaseCount).
construct_string_binary_several_soln_lookup_vector([Str - Soln | StrSolns],
NumOutVars, !MainRvalsCord, !.LaterNextRow, !LaterSolnArray,
!OneSolnCaseCount, !SeveralSolnsCaseCount) :-
StrRval = const(llconst_string(Str)),
(
Soln = one_soln(OutRvals),
!:OneSolnCaseCount = !.OneSolnCaseCount + 1,
ZeroRval = const(llconst_int(0)),
% The first ZeroRval means there is exactly one solution for this case;
% the second ZeroRval is a dummy that won't be referenced.
MainRow = [StrRval, ZeroRval, ZeroRval | OutRvals]
;
Soln = several_solns(FirstSolnRvals, LaterSolns),
!:SeveralSolnsCaseCount = !.SeveralSolnsCaseCount + 1,
list.length(LaterSolns, NumLaterSolns),
FirstRowOffset = !.LaterNextRow * NumOutVars,
LastRowOffset = (!.LaterNextRow + NumLaterSolns - 1) * NumOutVars,
FirstRowRval = const(llconst_int(FirstRowOffset)),
LastRowRval = const(llconst_int(LastRowOffset)),
MainRow = [StrRval, FirstRowRval, LastRowRval | FirstSolnRvals],
!:LaterNextRow = !.LaterNextRow + NumLaterSolns,
!:LaterSolnArray = !.LaterSolnArray ++ from_list(LaterSolns)
),
cord.snoc(MainRow, !MainRvalsCord),
construct_string_binary_several_soln_lookup_vector(StrSolns, NumOutVars,
!MainRvalsCord, !.LaterNextRow, !LaterSolnArray,
!OneSolnCaseCount, !SeveralSolnsCaseCount).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- type string_binary_switch_info
---> string_binary_switch_info(
sbsi_lo_reg :: lval,
sbsi_hi_reg :: lval,
sbsi_mid_reg :: lval,
sbsi_result_reg :: lval,
sbsi_loop_start_label :: label,
sbsi_gt_eq_label :: label,
sbsi_eq_label :: label,
sbsi_fail_label :: label,
sbsi_branch_start :: position_info,
sbsi_fail_code :: llds_code
).
:- pred init_string_binary_switch_info(can_fail::in,
string_binary_switch_info::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
init_string_binary_switch_info(CanFail, Info, !CI, !.CLD) :-
% We get the registers we use as working storage in the implementation
% of binary search, and in the implementation of trie and hash switches
% above. We get them now, before we generate the code of the switch arms,
% because the set of free registers will in general be different
% before and after that action. However, it is safe to release them
% immediately, even though we haven't yet generated all the code
% which uses them, because with all three implementation methods, that
% code will *only* be executed before the code for the cases.
% Releasing the registers early allows the code we generate for the cases
% to make use of them.
acquire_reg(reg_r, LoReg, !CLD),
acquire_reg(reg_r, HiReg, !CLD),
acquire_reg(reg_r, MidReg, !CLD),
acquire_reg(reg_r, ResultReg, !CLD),
release_reg(LoReg, !CLD),
release_reg(HiReg, !CLD),
release_reg(MidReg, !CLD),
release_reg(ResultReg, !CLD),
get_next_label(LoopStartLabel, !CI),
get_next_label(GtEqLabel, !CI),
get_next_label(EqLabel, !CI),
get_next_label(FailLabel, !CI),
% We must generate the failure code in the context in which
% none of the switch arms have been executed yet.
remember_position(!.CLD, BranchStart),
generate_string_switch_fail(CanFail, FailCode, !CI, !.CLD),
Info = string_binary_switch_info(LoReg, HiReg, MidReg, ResultReg,
LoopStartLabel, GtEqLabel, EqLabel, FailLabel, BranchStart, FailCode).
% Generate code for the binary search. This code will execute FailCode
% if the key is not in the table, and will fall through if it is, leaving
% the index of the matching row in the register specified by
% Info ^ sbsi_mid_reg.
%
:- pred generate_string_binary_switch_search(string_binary_switch_info::in,
rval::in, rval::in, array_elem_type::in, int::in, int::in,
llds_code::out) is det.
generate_string_binary_switch_search(Info, VarRval, TableAddrRval,
ArrayElemType, TableSize, NumColumns, Code) :-
Info = string_binary_switch_info(LoReg, HiReg, MidReg, ResultReg,
LoopStartLabel, GtEqLabel, EqLabel, FailLabel, _BranchStart, FailCode),
MaxIndex = TableSize - 1,
Code =
from_list([
llds_instr(assign(LoReg, const(llconst_int(0))), ""),
llds_instr(assign(HiReg, const(llconst_int(MaxIndex))), ""),
% LoopStartLabel
llds_instr(label(LoopStartLabel),
"begin table search loop, nofulljump"),
llds_instr(
if_val(
binop(int_cmp(int_type_int, gt), lval(LoReg), lval(HiReg)),
code_label(FailLabel)),
"have we searched all of the table?"),
llds_instr(
assign(MidReg,
binop(int_arith(int_type_int, ao_div),
binop(int_arith(int_type_int, ao_add),
lval(LoReg), lval(HiReg)),
const(llconst_int(2)))), ""),
llds_instr(
assign(ResultReg,
binop(str_nzp,
VarRval,
binop(array_index(ArrayElemType),
TableAddrRval,
binop(int_arith(int_type_int, ao_mul),
lval(MidReg),
const(llconst_int(NumColumns)))))),
"compare with the middle element"),
llds_instr(
if_val(
binop(int_cmp(int_type_int, ge),
lval(ResultReg), const(llconst_int(0))),
code_label(GtEqLabel)),
"branch away unless key is in lo half"),
llds_instr(
assign(HiReg,
binop(int_arith(int_type_int, ao_sub),
lval(MidReg), const(llconst_int(1)))),
""),
llds_instr(goto(code_label(LoopStartLabel)),
"go back to search the remaining lo half"),
% GtEqLabel
llds_instr(label(GtEqLabel), "nofulljump"),
llds_instr(
if_val(
binop(int_cmp(int_type_int, le),
lval(ResultReg), const(llconst_int(0))),
code_label(EqLabel)),
"branch away unless key is in hi half"),
llds_instr(
assign(LoReg,
binop(int_arith(int_type_int, ao_add),
lval(MidReg), const(llconst_int(1)))),
""),
llds_instr(goto(code_label(LoopStartLabel)),
"go back to search the remaining hi half"),
% FailLabel
llds_instr(label(FailLabel),
"handle the failure of the table search")
]) ++
FailCode ++
% EqLabel
singleton(
llds_instr(label(EqLabel), "we found the key")
).
%---------------------------------------------------------------------------%
:- pred generate_string_switch_fail(can_fail::in, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_string_switch_fail(CanFail, FailCode, !CI, !.CLD) :-
(
CanFail = can_fail,
generate_failure(FailCode, !CI, !.CLD)
;
CanFail = cannot_fail,
FailCode = singleton(
llds_instr(
comment("unreachable; fail code in cannot_fail switch"), "")
)
).
%---------------------------------------------------------------------------%
:- end_module ll_backend.string_switch.
%---------------------------------------------------------------------------%