mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-12 04:14:06 +00:00
1337 lines
43 KiB
Mathematica
1337 lines
43 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1994-1998 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% tag_switch.m - generate switches based on primary and secondary tags.
|
|
|
|
% Author: zs.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module tag_switch.
|
|
|
|
:- interface.
|
|
|
|
:- import_module hlds_goal, hlds_data, llds, switch_gen, code_info.
|
|
:- import_module list, term.
|
|
|
|
% Generate intelligent indexing code for tag based switches.
|
|
|
|
:- pred tag_switch__generate(list(extended_case), var, code_model, can_fail,
|
|
store_map, label, code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate(in, in, in, in, in, in, out, in, out) is det.
|
|
|
|
:- implementation.
|
|
|
|
:- import_module hlds_module, hlds_pred, code_gen, trace.
|
|
:- import_module options, globals, type_util, prog_data.
|
|
:- import_module assoc_list, map, tree, bool, int, string.
|
|
:- import_module require, std_util.
|
|
|
|
% where is the secondary tag (if any) for this primary tag value
|
|
:- type stag_loc ---> none ; local ; remote.
|
|
|
|
% map secondary tag values (-1 stands for none) to their goal
|
|
:- type stag_goal_map == map(int, hlds_goal).
|
|
:- type stag_goal_list == assoc_list(int, hlds_goal).
|
|
|
|
% map primary tag values to the set of their goals
|
|
:- type ptag_case_map == map(tag_bits, pair(stag_loc, stag_goal_map)).
|
|
:- type ptag_case_list == assoc_list(tag_bits,
|
|
pair(stag_loc, stag_goal_map)).
|
|
|
|
% map primary tag values to the number of constructors sharing them
|
|
:- type ptag_count_map == map(tag_bits, pair(stag_loc, int)).
|
|
:- type ptag_count_list == assoc_list(tag_bits, pair(stag_loc, int)).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% The idea is to generate two-level switches, first on the primary
|
|
% tag and then on the secondary tag. Since more than one function
|
|
% symbol can be eliminated by a failed primary tag test, this reduces
|
|
% the expected the number of comparisons required before finding the
|
|
% code corresponding to the actual value of the switch variable.
|
|
% We also get a speedup compared to non-tag switches by extracting
|
|
% the primary and secondary tags once instead of repeatedly for
|
|
% each functor test.
|
|
%
|
|
% We have four methods we can use for generating the code for the
|
|
% switches on both primary and secondary tags.
|
|
%
|
|
% 1. try-me-else chains have the form
|
|
%
|
|
% if (tag(var) != tag1) goto L1
|
|
% code for tag1
|
|
% goto end
|
|
% L1: if (tag(var) != tag2) goto L2
|
|
% code for tag2
|
|
% goto end
|
|
% L2: ...
|
|
% Ln: code for last possible tag value (or failure)
|
|
% goto end
|
|
%
|
|
% 2. try chains have the form
|
|
%
|
|
% if (tag(var) == tag1) goto L1
|
|
% if (tag(var) == tag2) goto L2
|
|
% ...
|
|
% code for last possible tag value (or failure)
|
|
% goto end
|
|
% L1: code for tag1
|
|
% goto end
|
|
% L2: code for tag2
|
|
% goto end
|
|
% ...
|
|
%
|
|
% 3. jump tables have the form
|
|
%
|
|
% goto tag(var) of L1, L2, ...
|
|
% L1: code for tag1
|
|
% goto end
|
|
% L2: code for tag2
|
|
% goto end
|
|
% ...
|
|
%
|
|
% 4. binary search switches have the form
|
|
%
|
|
% if (tag(var)) > 1) goto L23
|
|
% if (tag(var)) != 0) goto L1
|
|
% code for tag 0
|
|
% goto end
|
|
% L1: code for tag 1
|
|
% goto end
|
|
% L23: if (tag(var)) != 2) goto L3
|
|
% code for tag 2
|
|
% goto end
|
|
% L3: code for tag 3
|
|
% goto end
|
|
%
|
|
% Note that for a det switch with two tag values, try-me-else chains
|
|
% and try chains are equivalent.
|
|
|
|
% Which method is best depends on the number of possible tag values,
|
|
% on the costs of taken/untaken branches and table lookups on the given
|
|
% architecture, and on the frequency with which the various
|
|
% alternatives are taken.
|
|
%
|
|
% While the first two are in principle known at compile time,
|
|
% the third is not. Nevertheless, for switches on primary tags
|
|
% we can use the heuristic that the more secondary tags assigned to
|
|
% a primary tag, the more likely that the switch variable will have
|
|
% that primary tag at runtime.
|
|
%
|
|
% Try chains are good for switches with small numbers of alternatives
|
|
% on architectures where untaken branches are cheaper than taken
|
|
% branches.
|
|
%
|
|
% Try-me-else chains are good for switches with very small numbers of
|
|
% alternatives on architectures where taken branches are cheaper than
|
|
% untaken branches (which are rare these days).
|
|
%
|
|
% Jump tables are good for switches with large numbers of alternatives.
|
|
% The cost of jumping through a jump table is relatively high, since
|
|
% it involves a memory access and an indirect branch (which most
|
|
% current architectures do not handle well), but this cost is
|
|
% independent of the number of alternatives.
|
|
%
|
|
% Binary search switches are good for switches where the number of
|
|
% alternatives is large enough for the reduced expected number of
|
|
% branches executed to overcome the extra overhead of the subtraction
|
|
% required for some conditional branches (compared to try chains
|
|
% and try-me-else chains), but not large enough to make the
|
|
% expected cost of the expected number of comparisons exceed the
|
|
% expected cost of a jump table lookup and dispatch.
|
|
|
|
% For try-me-else chains, we want tag1 to be the most frequent case,
|
|
% tag 2 the next most frequent case, etc.
|
|
%
|
|
% For det try chains, we want the last tag value to be the most
|
|
% frequent case, since it can be reached without taken jumps.
|
|
% We want tag1 to be the next most frequent, tag2 the next most
|
|
% frequent after that, etc.
|
|
%
|
|
% For semidet try chains, there is no last possible tag value (the
|
|
% code for failure occupies its position), so we want tag1 to be
|
|
% the most frequent case, tag 2 the next most frequent case, etc.
|
|
%
|
|
% For jump tables, the position of the labels in the computed goto
|
|
% must conform to their numerical value. The order of the code
|
|
% fragments does not really matter, although the last has a slight
|
|
% edge in that no goto is needed to reach the code following the
|
|
% switch. If there is no code following the switch (which happens
|
|
% very frequently), then even this advantage is nullified.
|
|
%
|
|
% For binary search switches, we want the case of the most frequently
|
|
% occurring tag to be the first, since this code is reached with no
|
|
% taken branches and ends with an unconditional branch, whereas
|
|
% reaching the code of the other cases requires at least one taken
|
|
% *conditional* branch. In general, at each binary decision we
|
|
% want the more frequently reached cases to be in the half that
|
|
% immediately follows the if statement implementing the decision.
|
|
|
|
:- type switch_method ---> try_me_else_chain
|
|
; try_chain
|
|
; jump_table
|
|
; binary_search.
|
|
|
|
tag_switch__generate(Cases, Var, CodeModel, CanFail, StoreMap, EndLabel, Code)
|
|
-->
|
|
% group the cases based on primary tag value
|
|
% and find out how many constructors share each primary tag value
|
|
|
|
code_info__get_module_info(ModuleInfo),
|
|
code_info__get_proc_info(ProcInfo),
|
|
{ proc_info_vartypes(ProcInfo, VarTypes) },
|
|
{ map__lookup(VarTypes, Var, Type) },
|
|
{ tag_switch__get_ptag_counts(Type, ModuleInfo,
|
|
MaxPrimary, PtagCountMap) },
|
|
{ map__to_assoc_list(PtagCountMap, PtagCountList) },
|
|
{ map__init(PtagCaseMap0) },
|
|
{ tag_switch__group_cases_by_ptag(Cases, PtagCaseMap0, PtagCaseMap) },
|
|
|
|
{ map__count(PtagCaseMap, PtagsUsed) },
|
|
code_info__get_globals(Globals),
|
|
{ globals__lookup_int_option(Globals, dense_switch_size,
|
|
DenseSwitchSize) },
|
|
{ globals__lookup_int_option(Globals, try_switch_size,
|
|
TrySwitchSize) },
|
|
{ globals__lookup_int_option(Globals, binary_switch_size,
|
|
BinarySwitchSize) },
|
|
( { PtagsUsed >= DenseSwitchSize } ->
|
|
{ PrimaryMethod = jump_table }
|
|
; { PtagsUsed >= BinarySwitchSize } ->
|
|
{ PrimaryMethod = binary_search }
|
|
; { PtagsUsed >= TrySwitchSize } ->
|
|
{ PrimaryMethod = try_chain }
|
|
;
|
|
{ PrimaryMethod = try_me_else_chain }
|
|
),
|
|
|
|
% We get a register for holding the tag. The tag is needed only
|
|
% by the switch, and no other code gets control between producing
|
|
% the tag value and all uses of it, so we can release the register
|
|
% for use by the code of the various cases.
|
|
|
|
% We forgo using the register if the primary tag is needed only once,
|
|
% or if the "register" we get is likely to be slower than
|
|
% recomputing the tag from scratch.
|
|
|
|
code_info__produce_variable_in_reg(Var, VarCode, VarRval),
|
|
code_info__acquire_reg(r, PtagReg),
|
|
code_info__release_reg(PtagReg),
|
|
{
|
|
PrimaryMethod \= jump_table,
|
|
PtagsUsed >= 2,
|
|
globals__lookup_int_option(Globals, num_real_r_regs,
|
|
NumRealRegs),
|
|
(
|
|
NumRealRegs = 0
|
|
;
|
|
( PtagReg = reg(r, PtagRegNo) ->
|
|
PtagRegNo =< NumRealRegs
|
|
;
|
|
error("improper reg in tag switch")
|
|
)
|
|
)
|
|
->
|
|
PtagCode = node([
|
|
assign(PtagReg, unop(tag, VarRval))
|
|
- "compute tag to switch on"
|
|
]),
|
|
PtagRval = lval(PtagReg)
|
|
;
|
|
PtagCode = empty,
|
|
PtagRval = unop(tag, VarRval)
|
|
},
|
|
|
|
% We generate FailCode and EndCode here because the last case within
|
|
% a primary tag may not be the last case overall.
|
|
|
|
code_info__get_next_label(FailLabel),
|
|
{ FailLabelCode = node([
|
|
label(FailLabel) -
|
|
"switch has failed"
|
|
]) },
|
|
(
|
|
{ CanFail = cannot_fail },
|
|
{ FailCode = node([
|
|
goto(do_not_reached) - "oh-oh, det switch failed"
|
|
]) }
|
|
;
|
|
{ CanFail = can_fail },
|
|
code_info__generate_failure(FailCode)
|
|
),
|
|
{ LabelledFailCode = tree(FailLabelCode, FailCode) },
|
|
|
|
{ EndCode = node([label(EndLabel) - "end of tag switch"]) },
|
|
|
|
(
|
|
{ PrimaryMethod = binary_search },
|
|
{ tag_switch__order_ptags_by_value(0, MaxPrimary, PtagCaseMap,
|
|
PtagCaseList) },
|
|
tag_switch__generate_primary_binary_search(PtagCaseList,
|
|
0, MaxPrimary, PtagRval, VarRval, CodeModel, CanFail,
|
|
StoreMap, EndLabel, FailLabel, PtagCountMap,
|
|
no, MaybeFinalCodeInfo, CasesCode),
|
|
( { MaybeFinalCodeInfo = yes(FinalCodeInfo) } ->
|
|
code_info__slap_code_info(FinalCodeInfo)
|
|
;
|
|
{ error("binary search switch has no useful cases") }
|
|
)
|
|
;
|
|
{ PrimaryMethod = jump_table },
|
|
{ tag_switch__order_ptags_by_value(0, MaxPrimary, PtagCaseMap,
|
|
PtagCaseList) },
|
|
tag_switch__generate_primary_jump_table(PtagCaseList,
|
|
0, MaxPrimary, VarRval, CodeModel, StoreMap,
|
|
EndLabel, FailLabel, PtagCountMap, Labels, TableCode),
|
|
{ SwitchCode = node([
|
|
computed_goto(PtagRval, Labels) -
|
|
"switch on primary tag"
|
|
]) },
|
|
{ CasesCode = tree(SwitchCode, TableCode) }
|
|
;
|
|
{ PrimaryMethod = try_chain },
|
|
{ tag_switch__order_ptags_by_count(PtagCountList, PtagCaseMap,
|
|
PtagCaseList0) },
|
|
{
|
|
CanFail = cannot_fail,
|
|
PtagCaseList0 = [MostFreqCase | OtherCases]
|
|
->
|
|
list__append(OtherCases, [MostFreqCase], PtagCaseList)
|
|
;
|
|
PtagCaseList = PtagCaseList0
|
|
},
|
|
tag_switch__generate_primary_try_chain(PtagCaseList,
|
|
PtagRval, VarRval, CodeModel, CanFail, StoreMap,
|
|
EndLabel, FailLabel, PtagCountMap, empty, empty,
|
|
CasesCode)
|
|
;
|
|
{ PrimaryMethod = try_me_else_chain },
|
|
{ tag_switch__order_ptags_by_count(PtagCountList, PtagCaseMap,
|
|
PtagCaseList) },
|
|
tag_switch__generate_primary_try_me_else_chain(PtagCaseList,
|
|
PtagRval, VarRval, CodeModel, CanFail, StoreMap,
|
|
EndLabel, FailLabel, PtagCountMap, CasesCode)
|
|
),
|
|
|
|
{ Code =
|
|
tree(VarCode,
|
|
tree(PtagCode,
|
|
tree(CasesCode,
|
|
tree(LabelledFailCode,
|
|
EndCode)))) }.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate a switch on a primary tag value using a try-me-else chain.
|
|
|
|
:- pred tag_switch__generate_primary_try_me_else_chain(ptag_case_list,
|
|
rval, rval, code_model, can_fail, store_map, label, label,
|
|
ptag_count_map, code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_primary_try_me_else_chain(in, in, in, in, in, in,
|
|
in, in, in, out, in, out) is det.
|
|
|
|
tag_switch__generate_primary_try_me_else_chain([], _, _, _, _, _, _, _, _, _)
|
|
-->
|
|
{ error("generate_primary_try_me_else_chain: empty switch") }.
|
|
tag_switch__generate_primary_try_me_else_chain([PtagGroup | PtagGroups],
|
|
TagRval, VarRval, CodeModel, CanFail, StoreMap,
|
|
EndLabel, FailLabel, PtagCountMap, Code) -->
|
|
{ PtagGroup = Primary - (StagLoc - StagGoalMap) },
|
|
{ map__lookup(PtagCountMap, Primary, CountInfo) },
|
|
{ CountInfo = StagLoc1 - MaxSecondary },
|
|
{ StagLoc = StagLoc1 ->
|
|
true
|
|
;
|
|
error("secondary tag locations differ in generate_primary_try_me_else_chain")
|
|
},
|
|
(
|
|
{ PtagGroups = [_|_] ; CanFail = can_fail }
|
|
->
|
|
code_info__grab_code_info(CodeInfo),
|
|
code_info__get_next_label(ElseLabel),
|
|
{ TestRval = binop(ne, TagRval,
|
|
unop(mktag, const(int_const(Primary)))) },
|
|
{ TestCode = node([
|
|
if_val(TestRval, label(ElseLabel)) -
|
|
"test primary tag only"
|
|
]) },
|
|
tag_switch__generate_primary_tag_code(StagGoalMap,
|
|
Primary, MaxSecondary, StagLoc, VarRval, CodeModel,
|
|
StoreMap, EndLabel, FailLabel, TagCode),
|
|
{ ElseCode = node([
|
|
label(ElseLabel) -
|
|
"handle next primary tag"
|
|
]) },
|
|
{ ThisTagCode =
|
|
tree(TestCode,
|
|
tree(TagCode,
|
|
ElseCode))
|
|
},
|
|
( { PtagGroups = [_|_] } ->
|
|
code_info__slap_code_info(CodeInfo),
|
|
tag_switch__generate_primary_try_me_else_chain(
|
|
PtagGroups, TagRval, VarRval, CodeModel,
|
|
CanFail, StoreMap, EndLabel, FailLabel,
|
|
PtagCountMap, OtherTagsCode),
|
|
{ Code = tree(ThisTagCode, OtherTagsCode) }
|
|
;
|
|
% FailLabel ought to be the next label anyway,
|
|
% so this goto will be optimized away (unless the
|
|
% layout of the failcode in the caller changes).
|
|
{ FailCode = node([
|
|
goto(label(FailLabel)) -
|
|
"primary tag with no code to handle it"
|
|
]) },
|
|
{ Code = tree(ThisTagCode, FailCode) }
|
|
)
|
|
;
|
|
tag_switch__generate_primary_tag_code(StagGoalMap,
|
|
Primary, MaxSecondary, StagLoc, VarRval, CodeModel,
|
|
StoreMap, EndLabel, FailLabel, ThisTagCode),
|
|
{ Code = ThisTagCode }
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate a switch on a primary tag value using a try chain.
|
|
|
|
:- pred tag_switch__generate_primary_try_chain(ptag_case_list,
|
|
rval, rval, code_model, can_fail, store_map, label, label,
|
|
ptag_count_map, code_tree, code_tree, code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_primary_try_chain(in, in, in, in, in, in,
|
|
in, in, in, in, in, out, in, out) is det.
|
|
|
|
tag_switch__generate_primary_try_chain([], _, _, _, _, _, _, _, _, _, _, _) -->
|
|
{ error("empty list in generate_primary_try_chain") }.
|
|
tag_switch__generate_primary_try_chain([PtagGroup | PtagGroups],
|
|
TagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel,
|
|
FailLabel, PtagCountMap, PrevTests0, PrevCases0, Code) -->
|
|
{ PtagGroup = Primary - (StagLoc - StagGoalMap) },
|
|
{ map__lookup(PtagCountMap, Primary, CountInfo) },
|
|
{ CountInfo = StagLoc1 - MaxSecondary },
|
|
{ StagLoc = StagLoc1 ->
|
|
true
|
|
;
|
|
error("secondary tag locations differ in generate_primary_try_chain")
|
|
},
|
|
(
|
|
{ PtagGroups = [_|_] ; CanFail = can_fail }
|
|
->
|
|
code_info__grab_code_info(CodeInfo),
|
|
code_info__get_next_label(ThisPtagLabel),
|
|
{ TestRval = binop(eq, TagRval,
|
|
unop(mktag, const(int_const(Primary)))) },
|
|
{ TestCode = node([
|
|
if_val(TestRval, label(ThisPtagLabel)) -
|
|
"test primary tag only"
|
|
]) },
|
|
{ LabelCode = node([
|
|
label(ThisPtagLabel) -
|
|
"this primary tag"
|
|
]) },
|
|
tag_switch__generate_primary_tag_code(StagGoalMap,
|
|
Primary, MaxSecondary, StagLoc, VarRval, CodeModel,
|
|
StoreMap, EndLabel, FailLabel, TagCode),
|
|
{ PrevTests = tree(PrevTests0, TestCode) },
|
|
{ PrevCases = tree(tree(LabelCode, TagCode), PrevCases0) },
|
|
( { PtagGroups = [_|_] } ->
|
|
code_info__slap_code_info(CodeInfo),
|
|
tag_switch__generate_primary_try_chain(PtagGroups,
|
|
TagRval, VarRval, CodeModel, CanFail, StoreMap,
|
|
EndLabel, FailLabel, PtagCountMap,
|
|
PrevTests, PrevCases, Code)
|
|
;
|
|
{ FailCode = node([
|
|
goto(label(FailLabel)) -
|
|
"primary tag with no code to handle it"
|
|
]) },
|
|
{ Code = tree(PrevTests, tree(FailCode, PrevCases)) }
|
|
)
|
|
;
|
|
{ Comment = node([
|
|
comment("fallthrough to last tag value") - ""
|
|
]) },
|
|
tag_switch__generate_primary_tag_code(StagGoalMap,
|
|
Primary, MaxSecondary, StagLoc, VarRval,
|
|
CodeModel, StoreMap, EndLabel, FailLabel,
|
|
TagCode),
|
|
{ Code =
|
|
tree(PrevTests0,
|
|
tree(Comment,
|
|
tree(TagCode,
|
|
PrevCases0)))
|
|
}
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate the cases for a primary tag using a dense jump table
|
|
% that has an entry for all possible primary tag values.
|
|
|
|
:- pred tag_switch__generate_primary_jump_table(ptag_case_list, int, int,
|
|
rval, code_model, store_map, label, label, ptag_count_map,
|
|
list(label), code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_primary_jump_table(in, in, in, in,
|
|
in, in, in, in, in, out, out, in, out) is det.
|
|
|
|
tag_switch__generate_primary_jump_table(PtagGroups, CurPrimary, MaxPrimary,
|
|
VarRval, CodeModel, StoreMap, EndLabel, FailLabel, PtagCountMap,
|
|
Labels, Code) -->
|
|
( { CurPrimary > MaxPrimary } ->
|
|
{ PtagGroups = [] ->
|
|
true
|
|
;
|
|
error("caselist not empty when reaching limiting primary tag")
|
|
},
|
|
{ Labels = [] },
|
|
{ Code = empty }
|
|
;
|
|
{ NextPrimary is CurPrimary + 1 },
|
|
( { PtagGroups = [CurPrimary - PrimaryInfo | PtagGroups1] } ->
|
|
{ PrimaryInfo = StagLoc - StagGoalMap },
|
|
{ map__lookup(PtagCountMap, CurPrimary, CountInfo) },
|
|
{ CountInfo = StagLoc1 - MaxSecondary },
|
|
{ StagLoc = StagLoc1 ->
|
|
true
|
|
;
|
|
error("secondary tag locations differ in generate_primary_jump_table")
|
|
},
|
|
code_info__get_next_label(NewLabel),
|
|
{ LabelCode = node([
|
|
label(NewLabel) -
|
|
"start of a case in primary tag switch"
|
|
]) },
|
|
( { PtagGroups1 = [] } ->
|
|
tag_switch__generate_primary_tag_code(
|
|
StagGoalMap, CurPrimary, MaxSecondary,
|
|
StagLoc, VarRval, CodeModel,
|
|
StoreMap, EndLabel, FailLabel,
|
|
ThisTagCode)
|
|
;
|
|
code_info__grab_code_info(CodeInfo),
|
|
tag_switch__generate_primary_tag_code(
|
|
StagGoalMap, CurPrimary, MaxSecondary,
|
|
StagLoc, VarRval, CodeModel,
|
|
StoreMap, EndLabel, FailLabel,
|
|
ThisTagCode),
|
|
code_info__slap_code_info(CodeInfo)
|
|
),
|
|
tag_switch__generate_primary_jump_table(PtagGroups1,
|
|
NextPrimary, MaxPrimary, VarRval, CodeModel,
|
|
StoreMap, EndLabel, FailLabel, PtagCountMap,
|
|
OtherLabels, OtherCode),
|
|
{ Labels = [NewLabel | OtherLabels] },
|
|
{ Code =
|
|
tree(LabelCode,
|
|
tree(ThisTagCode,
|
|
OtherCode))
|
|
}
|
|
;
|
|
tag_switch__generate_primary_jump_table(PtagGroups,
|
|
NextPrimary, MaxPrimary, VarRval, CodeModel,
|
|
StoreMap, EndLabel, FailLabel, PtagCountMap,
|
|
OtherLabels, Code),
|
|
{ Labels = [FailLabel | OtherLabels] }
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate the cases for a primary tag using a binary search.
|
|
% This invocation looks after primary tag values in the range
|
|
% MinPtag to MaxPtag (including both boundary values).
|
|
|
|
:- pred tag_switch__generate_primary_binary_search(ptag_case_list, int, int,
|
|
rval, rval, code_model, can_fail, store_map, label, label,
|
|
ptag_count_map, maybe(code_info), maybe(code_info),
|
|
code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_primary_binary_search(in, in, in,
|
|
in, in, in, in, in, in, in, in, in, out, out, in, out) is det.
|
|
|
|
tag_switch__generate_primary_binary_search(PtagGroups, MinPtag, MaxPtag,
|
|
PtagRval, VarRval, CodeModel, CanFail, StoreMap,
|
|
EndLabel, FailLabel, PtagCountMap,
|
|
MaybeFinalCodeInfo0, MaybeFinalCodeInfo, Code) -->
|
|
( { MinPtag = MaxPtag } ->
|
|
{ CurPrimary = MinPtag },
|
|
( { PtagGroups = [] } ->
|
|
% there is no code for this tag
|
|
(
|
|
{ CanFail = can_fail },
|
|
{ string__int_to_string(CurPrimary, PtagStr) },
|
|
{ string__append("no code for ptag ", PtagStr,
|
|
Comment) },
|
|
{ Code = node([
|
|
goto(label(FailLabel)) -
|
|
Comment
|
|
]) }
|
|
;
|
|
{ CanFail = cannot_fail },
|
|
{ Code = empty }
|
|
),
|
|
{ MaybeFinalCodeInfo = MaybeFinalCodeInfo0 }
|
|
; { PtagGroups = [CurPrimary - PrimaryInfo] } ->
|
|
{ PrimaryInfo = StagLoc - StagGoalMap },
|
|
{ map__lookup(PtagCountMap, CurPrimary, CountInfo) },
|
|
{ CountInfo = StagLoc1 - MaxSecondary },
|
|
{ StagLoc = StagLoc1 ->
|
|
true
|
|
;
|
|
error("secondary tag locations differ in generate_primary_jump_table")
|
|
},
|
|
tag_switch__generate_primary_tag_code(
|
|
StagGoalMap, CurPrimary, MaxSecondary,
|
|
StagLoc, VarRval, CodeModel,
|
|
StoreMap, EndLabel, FailLabel, Code),
|
|
code_info__grab_code_info(CodeInfo),
|
|
{ MaybeFinalCodeInfo = yes(CodeInfo) }
|
|
;
|
|
{ error("caselist not singleton or empty when binary search ends") }
|
|
)
|
|
;
|
|
{ LowRangeEnd is (MinPtag + MaxPtag) // 2 },
|
|
{ HighRangeStart is LowRangeEnd + 1 },
|
|
{ InLowGroup = lambda([PtagGroup::in] is semidet, (
|
|
PtagGroup = Ptag - _,
|
|
Ptag =< LowRangeEnd
|
|
)) },
|
|
{ list__filter(InLowGroup, PtagGroups, LowGroups, HighGroups) },
|
|
code_info__get_next_label(NewLabel),
|
|
{ string__int_to_string(MinPtag, LowStartStr) },
|
|
{ string__int_to_string(LowRangeEnd, LowEndStr) },
|
|
{ string__int_to_string(HighRangeStart, HighStartStr) },
|
|
{ string__int_to_string(MaxPtag, HighEndStr) },
|
|
{ string__append_list(["fallthrough for ptags ",
|
|
LowStartStr, " to ", LowEndStr], IfComment) },
|
|
{ string__append_list(["code for ptags ", HighStartStr,
|
|
" to ", HighEndStr], LabelComment) },
|
|
{ LowRangeEndConst = const(int_const(LowRangeEnd)) },
|
|
{ TestRval = binop(>, PtagRval, LowRangeEndConst) },
|
|
{ IfCode = node([
|
|
if_val(TestRval, label(NewLabel)) -
|
|
IfComment
|
|
]) },
|
|
{ LabelCode = node([
|
|
label(NewLabel) -
|
|
LabelComment
|
|
]) },
|
|
|
|
code_info__grab_code_info(CodeInfo),
|
|
tag_switch__generate_primary_binary_search(LowGroups,
|
|
MinPtag, LowRangeEnd, PtagRval, VarRval, CodeModel,
|
|
CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap,
|
|
MaybeFinalCodeInfo0, MaybeFinalCodeInfo1, LowRangeCode),
|
|
code_info__slap_code_info(CodeInfo),
|
|
tag_switch__generate_primary_binary_search(HighGroups,
|
|
HighRangeStart, MaxPtag, PtagRval, VarRval, CodeModel,
|
|
CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap,
|
|
MaybeFinalCodeInfo1, MaybeFinalCodeInfo, HighRangeCode),
|
|
|
|
{ Code =
|
|
tree(IfCode,
|
|
tree(LowRangeCode,
|
|
tree(LabelCode,
|
|
HighRangeCode)))
|
|
}
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate the code corresponding to a primary tag.
|
|
% If this primary tag has secondary tags, decide whether we should
|
|
% use a jump table to implement the secondary switch.
|
|
|
|
:- pred tag_switch__generate_primary_tag_code(stag_goal_map, tag_bits, int,
|
|
stag_loc, rval, code_model, store_map,
|
|
label, label, code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_primary_tag_code(in, in, in, in, in, in, in,
|
|
in, in, out, in, out) is det.
|
|
|
|
tag_switch__generate_primary_tag_code(GoalMap, Primary, MaxSecondary, StagLoc,
|
|
Rval, CodeModel, StoreMap, EndLabel, FailLabel, Code) -->
|
|
{ map__to_assoc_list(GoalMap, GoalList) },
|
|
(
|
|
{ StagLoc = none }
|
|
->
|
|
% There is no secondary tag, so there is no switch on it
|
|
( { GoalList = [-1 - Goal] } ->
|
|
trace__maybe_generate_internal_event_code(Goal,
|
|
TraceCode),
|
|
code_gen__generate_goal(CodeModel, Goal, GoalCode),
|
|
code_info__generate_branch_end(CodeModel, StoreMap,
|
|
SaveCode),
|
|
{ GotoCode = node([
|
|
goto(label(EndLabel)) -
|
|
"skip to end of primary tag switch"
|
|
]) },
|
|
{ Code =
|
|
tree(TraceCode,
|
|
tree(GoalCode,
|
|
tree(SaveCode,
|
|
GotoCode)))
|
|
}
|
|
; { GoalList = [] } ->
|
|
{ error("no goal for non-shared tag") }
|
|
;
|
|
{ error("more than one goal for non-shared tag") }
|
|
)
|
|
;
|
|
% There is a secondary tag, so figure out how to switch on it
|
|
code_info__get_globals(Globals),
|
|
{ globals__lookup_int_option(Globals, dense_switch_size,
|
|
DenseSwitchSize) },
|
|
{ globals__lookup_int_option(Globals, binary_switch_size,
|
|
BinarySwitchSize) },
|
|
{ globals__lookup_int_option(Globals, try_switch_size,
|
|
TrySwitchSize) },
|
|
{ MaxSecondary >= DenseSwitchSize ->
|
|
SecondaryMethod = jump_table
|
|
; MaxSecondary >= BinarySwitchSize ->
|
|
SecondaryMethod = binary_search
|
|
; MaxSecondary >= TrySwitchSize ->
|
|
SecondaryMethod = try_chain
|
|
;
|
|
SecondaryMethod = try_me_else_chain
|
|
},
|
|
|
|
{ StagLoc = remote ->
|
|
OrigStagRval = lval(field(yes(Primary), Rval,
|
|
const(int_const(0)))),
|
|
Comment = "compute remote sec tag to switch on"
|
|
;
|
|
OrigStagRval = unop(unmkbody, Rval),
|
|
Comment = "compute local sec tag to switch on"
|
|
},
|
|
|
|
code_info__acquire_reg(r, StagReg),
|
|
code_info__release_reg(StagReg),
|
|
{
|
|
SecondaryMethod \= jump_table,
|
|
MaxSecondary >= 2,
|
|
globals__lookup_int_option(Globals, num_real_r_regs,
|
|
NumRealRegs),
|
|
(
|
|
NumRealRegs = 0
|
|
;
|
|
( StagReg = reg(r, StagRegNo) ->
|
|
StagRegNo =< NumRealRegs
|
|
;
|
|
error("improper reg in tag switch")
|
|
)
|
|
)
|
|
->
|
|
StagCode = node([
|
|
assign(StagReg, OrigStagRval) -
|
|
Comment
|
|
]),
|
|
StagRval = lval(StagReg)
|
|
;
|
|
StagCode = empty,
|
|
StagRval = OrigStagRval
|
|
},
|
|
|
|
|
|
(
|
|
{ list__length(GoalList, GoalCount) },
|
|
{ FullGoalCount is MaxSecondary + 1 },
|
|
{ FullGoalCount = GoalCount }
|
|
->
|
|
{ CanFail = cannot_fail }
|
|
;
|
|
{ CanFail = can_fail }
|
|
),
|
|
|
|
(
|
|
{ SecondaryMethod = jump_table },
|
|
tag_switch__generate_secondary_jump_table(GoalList,
|
|
0, MaxSecondary, CodeModel, StoreMap,
|
|
EndLabel, FailLabel, Labels, CasesCode),
|
|
{ SwitchCode = node([
|
|
computed_goto(StagRval, Labels) -
|
|
"switch on secondary tag"
|
|
]) },
|
|
{ Code = tree(SwitchCode, CasesCode) }
|
|
;
|
|
{ SecondaryMethod = binary_search },
|
|
tag_switch__generate_secondary_binary_search(GoalList,
|
|
0, MaxSecondary, StagRval, CodeModel, CanFail,
|
|
StoreMap, EndLabel, FailLabel,
|
|
no, MaybeFinalCodeInfo, Code),
|
|
( { MaybeFinalCodeInfo = yes(FinalCodeInfo) } ->
|
|
code_info__slap_code_info(FinalCodeInfo)
|
|
;
|
|
{ error("binary search switch has no useful cases") }
|
|
)
|
|
;
|
|
{ SecondaryMethod = try_chain },
|
|
tag_switch__generate_secondary_try_chain(GoalList,
|
|
StagRval, CodeModel, CanFail, StoreMap,
|
|
EndLabel, FailLabel, empty, empty, Codes),
|
|
{ Code = tree(StagCode, Codes) }
|
|
;
|
|
{ SecondaryMethod = try_me_else_chain },
|
|
tag_switch__generate_secondary_try_me_else_chain(
|
|
GoalList, StagRval, CodeModel, CanFail,
|
|
StoreMap, EndLabel, FailLabel, Codes),
|
|
{ Code = tree(StagCode, Codes) }
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate a switch on a secondary tag value using a try-me-else chain.
|
|
|
|
:- pred tag_switch__generate_secondary_try_me_else_chain(stag_goal_list, rval,
|
|
code_model, can_fail, store_map, label, label,
|
|
code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_secondary_try_me_else_chain(in, in, in, in, in,
|
|
in, in, out, in, out) is det.
|
|
|
|
tag_switch__generate_secondary_try_me_else_chain([], _, _, _, _, _, _, _) -->
|
|
{ error("generate_secondary_try_me_else_chain: empty switch") }.
|
|
tag_switch__generate_secondary_try_me_else_chain([Case0 | Cases0], StagRval,
|
|
CodeModel, CanFail, StoreMap, EndLabel, FailLabel, Code) -->
|
|
{ Case0 = Secondary - Goal },
|
|
( { Cases0 = [_|_] ; CanFail = can_fail } ->
|
|
code_info__grab_code_info(CodeInfo),
|
|
code_info__get_next_label(ElseLabel),
|
|
{ TestCode = node([
|
|
if_val(binop(ne, StagRval,
|
|
const(int_const(Secondary))),
|
|
label(ElseLabel))
|
|
- "test remote sec tag only"
|
|
]) },
|
|
trace__maybe_generate_internal_event_code(Goal, TraceCode),
|
|
code_gen__generate_goal(CodeModel, Goal, GoalCode),
|
|
code_info__generate_branch_end(CodeModel, StoreMap, SaveCode),
|
|
{ GotoLabelCode = node([
|
|
goto(label(EndLabel)) -
|
|
"skip to end of secondary tag switch",
|
|
label(ElseLabel) -
|
|
"handle next secondary tag"
|
|
]) },
|
|
{ ThisCode =
|
|
tree(TestCode,
|
|
tree(TraceCode,
|
|
tree(GoalCode,
|
|
tree(SaveCode,
|
|
GotoLabelCode))))
|
|
},
|
|
( { Cases0 = [_|_] } ->
|
|
code_info__slap_code_info(CodeInfo),
|
|
tag_switch__generate_secondary_try_me_else_chain(Cases0,
|
|
StagRval, CodeModel, CanFail, StoreMap,
|
|
EndLabel, FailLabel, OtherCode),
|
|
{ Code = tree(ThisCode, OtherCode) }
|
|
;
|
|
{ FailCode = node([
|
|
goto(label(FailLabel)) -
|
|
"secondary tag does not match"
|
|
]) },
|
|
{ Code = tree(ThisCode, FailCode) }
|
|
)
|
|
;
|
|
trace__maybe_generate_internal_event_code(Goal, TraceCode),
|
|
code_gen__generate_goal(CodeModel, Goal, GoalCode),
|
|
code_info__generate_branch_end(CodeModel, StoreMap, SaveCode),
|
|
{ GotoCode = node([
|
|
goto(label(EndLabel)) -
|
|
"skip to end of secondary tag switch"
|
|
]) },
|
|
{ Code =
|
|
tree(TraceCode,
|
|
tree(GoalCode,
|
|
tree(SaveCode,
|
|
GotoCode)))
|
|
}
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate a switch on a secondary tag value using a try chain.
|
|
|
|
:- pred tag_switch__generate_secondary_try_chain(stag_goal_list, rval,
|
|
code_model, can_fail, store_map, label, label,
|
|
code_tree, code_tree, code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_secondary_try_chain(in, in, in, in, in,
|
|
in, in, in, in, out, in, out) is det.
|
|
|
|
tag_switch__generate_secondary_try_chain([], _, _, _, _, _, _, _, _, _) -->
|
|
{ error("generate_secondary_try_chain: empty switch") }.
|
|
tag_switch__generate_secondary_try_chain([Case0 | Cases0], StagRval,
|
|
CodeModel, CanFail, StoreMap, EndLabel, FailLabel,
|
|
PrevTests0, PrevCases0, Code) -->
|
|
{ Case0 = Secondary - Goal },
|
|
( { Cases0 = [_|_] ; CanFail = can_fail } ->
|
|
code_info__grab_code_info(CodeInfo),
|
|
code_info__get_next_label(ThisStagLabel),
|
|
{ TestCode = node([
|
|
if_val(binop(eq, StagRval,
|
|
const(int_const(Secondary))),
|
|
label(ThisStagLabel))
|
|
- "test remote sec tag only"
|
|
]) },
|
|
{ LabelCode = node([
|
|
label(ThisStagLabel) -
|
|
"handle next secondary tag"
|
|
]) },
|
|
trace__maybe_generate_internal_event_code(Goal, TraceCode),
|
|
code_gen__generate_goal(CodeModel, Goal, GoalCode),
|
|
code_info__generate_branch_end(CodeModel, StoreMap, SaveCode),
|
|
{ GotoCode = node([
|
|
goto(label(EndLabel)) -
|
|
"skip to end of secondary tag switch"
|
|
]) },
|
|
{ ThisCode =
|
|
tree(LabelCode,
|
|
tree(TraceCode,
|
|
tree(GoalCode,
|
|
tree(SaveCode,
|
|
GotoCode))))
|
|
},
|
|
{ PrevTests = tree(PrevTests0, TestCode) },
|
|
{ PrevCases = tree(ThisCode, PrevCases0) },
|
|
( { Cases0 = [_|_] } ->
|
|
code_info__slap_code_info(CodeInfo),
|
|
tag_switch__generate_secondary_try_chain(Cases0,
|
|
StagRval, CodeModel, CanFail, StoreMap,
|
|
EndLabel, FailLabel,
|
|
PrevTests, PrevCases, Code)
|
|
;
|
|
{ FailCode = node([
|
|
goto(label(FailLabel)) -
|
|
"secondary tag with no code to handle it"
|
|
]) },
|
|
{ Code = tree(PrevTests, tree(FailCode, PrevCases)) }
|
|
)
|
|
;
|
|
trace__maybe_generate_internal_event_code(Goal, TraceCode),
|
|
code_gen__generate_goal(CodeModel, Goal, GoalCode),
|
|
code_info__generate_branch_end(CodeModel, StoreMap, SaveCode),
|
|
{ GotoCode = node([
|
|
goto(label(EndLabel)) -
|
|
"skip to end of secondary tag switch"
|
|
]) },
|
|
{ Code =
|
|
tree(PrevTests0,
|
|
tree(TraceCode,
|
|
tree(GoalCode,
|
|
tree(SaveCode,
|
|
tree(GotoCode,
|
|
PrevCases0)))))
|
|
}
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate the cases for a primary tag using a dense jump table
|
|
% that has an entry for all possible secondary tag values.
|
|
|
|
:- pred tag_switch__generate_secondary_jump_table(stag_goal_list, int, int,
|
|
code_model, store_map, label, label, list(label),
|
|
code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_secondary_jump_table(in, in, in, in,
|
|
in, in, in, out, out, in, out) is det.
|
|
|
|
tag_switch__generate_secondary_jump_table(CaseList, CurSecondary, MaxSecondary,
|
|
CodeModel, StoreMap, EndLabel, FailLabel, Labels, Code) -->
|
|
( { CurSecondary > MaxSecondary } ->
|
|
{ CaseList = [] ->
|
|
true
|
|
;
|
|
error("caselist not empty when reaching limiting secondary tag")
|
|
},
|
|
{ Labels = [] },
|
|
{ Code = empty }
|
|
;
|
|
{ NextSecondary is CurSecondary + 1 },
|
|
( { CaseList = [CurSecondary - Goal | CaseList1] } ->
|
|
code_info__get_next_label(NewLabel),
|
|
{ LabelCode = node([
|
|
label(NewLabel) -
|
|
"start of case in secondary tag switch"
|
|
]) },
|
|
code_info__grab_code_info(CodeInfo),
|
|
trace__maybe_generate_internal_event_code(Goal,
|
|
TraceCode),
|
|
code_gen__generate_goal(CodeModel, Goal, GoalCode),
|
|
code_info__generate_branch_end(CodeModel, StoreMap,
|
|
SaveCode),
|
|
( { CaseList1 = [] } ->
|
|
[]
|
|
;
|
|
code_info__slap_code_info(CodeInfo)
|
|
),
|
|
{ GotoCode = node([
|
|
goto(label(EndLabel)) -
|
|
"branch to end of tag switch"
|
|
]) },
|
|
tag_switch__generate_secondary_jump_table(CaseList1,
|
|
NextSecondary, MaxSecondary, CodeModel,
|
|
StoreMap, EndLabel, FailLabel, OtherLabels,
|
|
OtherCode),
|
|
{ Labels = [NewLabel | OtherLabels] },
|
|
{ Code =
|
|
tree(LabelCode,
|
|
tree(TraceCode,
|
|
tree(GoalCode,
|
|
tree(SaveCode,
|
|
tree(GotoCode,
|
|
OtherCode)))))
|
|
}
|
|
;
|
|
tag_switch__generate_secondary_jump_table(CaseList,
|
|
NextSecondary, MaxSecondary, CodeModel,
|
|
StoreMap, EndLabel, FailLabel, OtherLabels,
|
|
Code),
|
|
{ Labels = [FailLabel | OtherLabels] }
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate the cases for a secondary tag using a binary search.
|
|
% This invocation looks after secondary tag values in the range
|
|
% MinPtag to MaxPtag (including both boundary values).
|
|
|
|
:- pred tag_switch__generate_secondary_binary_search(stag_goal_list, int, int,
|
|
rval, code_model, can_fail, store_map, label, label,
|
|
maybe(code_info), maybe(code_info),
|
|
code_tree, code_info, code_info).
|
|
:- mode tag_switch__generate_secondary_binary_search(in, in, in,
|
|
in, in, in, in, in, in, in, out, out, in, out) is det.
|
|
|
|
tag_switch__generate_secondary_binary_search(StagGoals, MinStag, MaxStag,
|
|
StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel,
|
|
MaybeFinalCodeInfo0, MaybeFinalCodeInfo, Code) -->
|
|
( { MinStag = MaxStag } ->
|
|
{ CurSec = MinStag },
|
|
( { StagGoals = [] } ->
|
|
% there is no code for this tag
|
|
(
|
|
{ CanFail = can_fail },
|
|
{ string__int_to_string(CurSec, StagStr) },
|
|
{ string__append("no code for ptag ", StagStr,
|
|
Comment) },
|
|
{ Code = node([
|
|
goto(label(FailLabel)) -
|
|
Comment
|
|
]) }
|
|
;
|
|
{ CanFail = cannot_fail },
|
|
{ Code = empty }
|
|
),
|
|
{ MaybeFinalCodeInfo = MaybeFinalCodeInfo0 }
|
|
; { StagGoals = [CurSec - Goal] } ->
|
|
trace__maybe_generate_internal_event_code(Goal,
|
|
TraceCode),
|
|
code_gen__generate_goal(CodeModel, Goal, GoalCode),
|
|
code_info__generate_branch_end(CodeModel, StoreMap,
|
|
SaveCode),
|
|
{ Code =
|
|
tree(TraceCode,
|
|
tree(GoalCode,
|
|
SaveCode))
|
|
},
|
|
code_info__grab_code_info(CodeInfo),
|
|
{ MaybeFinalCodeInfo = yes(CodeInfo) }
|
|
;
|
|
{ error("goallist not singleton or empty when binary search ends") }
|
|
)
|
|
;
|
|
{ LowRangeEnd is (MinStag + MaxStag) // 2 },
|
|
{ HighRangeStart is LowRangeEnd + 1 },
|
|
{ InLowGroup = lambda([StagGoal::in] is semidet, (
|
|
StagGoal = Stag - _,
|
|
Stag =< LowRangeEnd
|
|
)) },
|
|
{ list__filter(InLowGroup, StagGoals, LowGoals, HighGoals) },
|
|
code_info__get_next_label(NewLabel),
|
|
{ string__int_to_string(MinStag, LowStartStr) },
|
|
{ string__int_to_string(LowRangeEnd, LowEndStr) },
|
|
{ string__int_to_string(HighRangeStart, HighStartStr) },
|
|
{ string__int_to_string(MaxStag, HighEndStr) },
|
|
{ string__append_list(["fallthrough for stags ",
|
|
LowStartStr, " to ", LowEndStr], IfComment) },
|
|
{ string__append_list(["code for stags ", HighStartStr,
|
|
" to ", HighEndStr], LabelComment) },
|
|
{ LowRangeEndConst = const(int_const(LowRangeEnd)) },
|
|
{ TestRval = binop(>, StagRval, LowRangeEndConst) },
|
|
{ IfCode = node([
|
|
if_val(TestRval, label(NewLabel)) -
|
|
IfComment
|
|
]) },
|
|
{ LabelCode = node([
|
|
label(NewLabel) -
|
|
LabelComment
|
|
]) },
|
|
|
|
code_info__grab_code_info(CodeInfo),
|
|
tag_switch__generate_secondary_binary_search(LowGoals,
|
|
MinStag, LowRangeEnd, StagRval, CodeModel,
|
|
CanFail, StoreMap, EndLabel, FailLabel,
|
|
MaybeFinalCodeInfo0, MaybeFinalCodeInfo1, LowRangeCode),
|
|
code_info__slap_code_info(CodeInfo),
|
|
tag_switch__generate_secondary_binary_search(HighGoals,
|
|
HighRangeStart, MaxStag, StagRval, CodeModel,
|
|
CanFail, StoreMap, EndLabel, FailLabel,
|
|
MaybeFinalCodeInfo1, MaybeFinalCodeInfo, HighRangeCode),
|
|
|
|
{ Code =
|
|
tree(IfCode,
|
|
tree(LowRangeCode,
|
|
tree(LabelCode,
|
|
HighRangeCode)))
|
|
}
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Find out how many secondary tags share each primary tag
|
|
% of the given variable.
|
|
|
|
:- pred tag_switch__get_ptag_counts(type, module_info, int, ptag_count_map).
|
|
:- mode tag_switch__get_ptag_counts(in, in, out, out) is det.
|
|
|
|
tag_switch__get_ptag_counts(Type, ModuleInfo, MaxPrimary, PtagCountMap) :-
|
|
( type_to_type_id(Type, TypeIdPrime, _) ->
|
|
TypeId = TypeIdPrime
|
|
;
|
|
error("unknown type in tag_switch__get_ptag_counts")
|
|
),
|
|
module_info_types(ModuleInfo, TypeTable),
|
|
map__lookup(TypeTable, TypeId, TypeDefn),
|
|
hlds_data__get_type_defn_body(TypeDefn, Body),
|
|
( Body = du_type(_, ConsTable, _, _) ->
|
|
map__to_assoc_list(ConsTable, ConsList),
|
|
tag_switch__cons_list_to_tag_list(ConsList, TagList)
|
|
;
|
|
error("non-du type in tag_switch__get_ptag_counts")
|
|
),
|
|
map__init(PtagCountMap0),
|
|
tag_switch__get_ptag_counts_2(TagList, -1, MaxPrimary,
|
|
PtagCountMap0, PtagCountMap).
|
|
|
|
:- pred tag_switch__get_ptag_counts_2(list(cons_tag), int, int,
|
|
ptag_count_map, ptag_count_map).
|
|
:- mode tag_switch__get_ptag_counts_2(in, in, out, in, out) is det.
|
|
|
|
tag_switch__get_ptag_counts_2([], Max, Max, PtagCountMap, PtagCountMap).
|
|
tag_switch__get_ptag_counts_2([ConsTag | TagList], MaxPrimary0, MaxPrimary,
|
|
PtagCountMap0, PtagCountMap) :-
|
|
( ConsTag = simple_tag(Primary) ->
|
|
int__max(MaxPrimary0, Primary, MaxPrimary1),
|
|
( map__search(PtagCountMap0, Primary, _) ->
|
|
error("simple tag is shared")
|
|
;
|
|
map__det_insert(PtagCountMap0, Primary, none - (-1),
|
|
PtagCountMap1)
|
|
)
|
|
; ConsTag = complicated_tag(Primary, Secondary) ->
|
|
int__max(MaxPrimary0, Primary, MaxPrimary1),
|
|
( map__search(PtagCountMap0, Primary, Target) ->
|
|
Target = TagType - MaxSoFar,
|
|
( TagType = remote ->
|
|
true
|
|
;
|
|
error("remote tag is shared with non-remote")
|
|
),
|
|
int__max(Secondary, MaxSoFar, Max),
|
|
map__det_update(PtagCountMap0, Primary, remote - Max,
|
|
PtagCountMap1)
|
|
;
|
|
map__det_insert(PtagCountMap0, Primary,
|
|
remote - Secondary, PtagCountMap1)
|
|
)
|
|
; ConsTag = complicated_constant_tag(Primary, Secondary) ->
|
|
int__max(MaxPrimary0, Primary, MaxPrimary1),
|
|
( map__search(PtagCountMap0, Primary, Target) ->
|
|
Target = TagType - MaxSoFar,
|
|
( TagType = local ->
|
|
true
|
|
;
|
|
error("local tag is shared with non-local")
|
|
),
|
|
int__max(Secondary, MaxSoFar, Max),
|
|
map__det_update(PtagCountMap0, Primary, local - Max,
|
|
PtagCountMap1)
|
|
;
|
|
map__det_insert(PtagCountMap0, Primary,
|
|
local - Secondary, PtagCountMap1)
|
|
)
|
|
;
|
|
error("non-du tag in tag_switch__get_ptag_counts_2")
|
|
),
|
|
tag_switch__get_ptag_counts_2(TagList, MaxPrimary1, MaxPrimary,
|
|
PtagCountMap1, PtagCountMap).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Group together all the cases that depend on the given variable
|
|
% having the same primary tag value.
|
|
|
|
:- pred tag_switch__group_cases_by_ptag(cases_list,
|
|
ptag_case_map, ptag_case_map).
|
|
:- mode tag_switch__group_cases_by_ptag(in, in, out) is det.
|
|
|
|
tag_switch__group_cases_by_ptag([], PtagCaseMap, PtagCaseMap).
|
|
tag_switch__group_cases_by_ptag([Case0 | Cases0], PtagCaseMap0, PtagCaseMap) :-
|
|
Case0 = case(_Priority, Tag, _ConsId, Goal),
|
|
( Tag = simple_tag(Primary) ->
|
|
( map__search(PtagCaseMap0, Primary, _Group) ->
|
|
error("simple tag is shared")
|
|
;
|
|
map__init(StagGoalMap0),
|
|
map__det_insert(StagGoalMap0, -1, Goal, StagGoalMap),
|
|
map__det_insert(PtagCaseMap0, Primary,
|
|
none - StagGoalMap, PtagCaseMap1)
|
|
)
|
|
; Tag = complicated_tag(Primary, Secondary) ->
|
|
( map__search(PtagCaseMap0, Primary, Group) ->
|
|
Group = StagLoc - StagGoalMap0,
|
|
( StagLoc = remote ->
|
|
true
|
|
;
|
|
error("remote tag is shared with non-remote")
|
|
),
|
|
map__det_insert(StagGoalMap0, Secondary, Goal,
|
|
StagGoalMap),
|
|
map__det_update(PtagCaseMap0, Primary,
|
|
remote - StagGoalMap, PtagCaseMap1)
|
|
;
|
|
map__init(StagGoalMap0),
|
|
map__det_insert(StagGoalMap0, Secondary, Goal,
|
|
StagGoalMap),
|
|
map__det_insert(PtagCaseMap0, Primary,
|
|
remote - StagGoalMap, PtagCaseMap1)
|
|
)
|
|
; Tag = complicated_constant_tag(Primary, Secondary) ->
|
|
( map__search(PtagCaseMap0, Primary, Group) ->
|
|
Group = StagLoc - StagGoalMap0,
|
|
( StagLoc = local ->
|
|
true
|
|
;
|
|
error("local tag is shared with non-local")
|
|
),
|
|
map__det_insert(StagGoalMap0, Secondary, Goal,
|
|
StagGoalMap),
|
|
map__det_update(PtagCaseMap0, Primary,
|
|
local - StagGoalMap, PtagCaseMap1)
|
|
;
|
|
map__init(StagGoalMap0),
|
|
map__det_insert(StagGoalMap0, Secondary, Goal,
|
|
StagGoalMap),
|
|
map__det_insert(PtagCaseMap0, Primary,
|
|
local - StagGoalMap, PtagCaseMap1)
|
|
)
|
|
;
|
|
error("non-du tag in tag_switch__group_cases_by_ptag")
|
|
),
|
|
tag_switch__group_cases_by_ptag(Cases0, PtagCaseMap1, PtagCaseMap).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Order the primary tags based on the number of secondary tags
|
|
% associated with them, putting the ones with the most secondary tags
|
|
% first. We use selection sort.
|
|
% Note that it is not an error for a primary tag to have no case list;
|
|
% this can happen in semideterministic switches, or in det switches
|
|
% where the initial inst of the switch variable is a bound(...) inst
|
|
% representing a subtype.
|
|
|
|
:- pred tag_switch__order_ptags_by_count(ptag_count_list, ptag_case_map,
|
|
ptag_case_list).
|
|
:- mode tag_switch__order_ptags_by_count(in, in, out) is det.
|
|
|
|
tag_switch__order_ptags_by_count(PtagCountList0, PtagCaseMap0, PtagCaseList) :-
|
|
(
|
|
tag_switch__select_frequent_ptag(PtagCountList0,
|
|
Primary, _, PtagCountList1)
|
|
->
|
|
( map__search(PtagCaseMap0, Primary, PtagCase) ->
|
|
map__delete(PtagCaseMap0, Primary, PtagCaseMap1),
|
|
tag_switch__order_ptags_by_count(PtagCountList1,
|
|
PtagCaseMap1, PtagCaseList1),
|
|
PtagCaseList = [Primary - PtagCase | PtagCaseList1]
|
|
;
|
|
tag_switch__order_ptags_by_count(PtagCountList1,
|
|
PtagCaseMap0, PtagCaseList)
|
|
)
|
|
;
|
|
( map__is_empty(PtagCaseMap0) ->
|
|
PtagCaseList = []
|
|
;
|
|
error("PtagCaseMap0 is not empty in tag_switch__order_ptags_by_count")
|
|
)
|
|
).
|
|
|
|
% Select the most frequently used primary tag based on the number of
|
|
% secondary tags associated with it.
|
|
|
|
:- pred tag_switch__select_frequent_ptag(ptag_count_list, tag_bits, int,
|
|
ptag_count_list).
|
|
:- mode tag_switch__select_frequent_ptag(in, out, out, out) is semidet.
|
|
|
|
tag_switch__select_frequent_ptag([PtagCount0 | PtagCountList1], Primary, Count,
|
|
PtagCountList) :-
|
|
PtagCount0 = Primary0 - (_ - Count0),
|
|
(
|
|
tag_switch__select_frequent_ptag(PtagCountList1,
|
|
Primary1, Count1, PtagCountList2),
|
|
Count1 > Count0
|
|
->
|
|
Primary = Primary1,
|
|
Count = Count1,
|
|
PtagCountList = [PtagCount0 | PtagCountList2]
|
|
;
|
|
Primary = Primary0,
|
|
Count = Count0,
|
|
PtagCountList = PtagCountList1
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Order the primary tags based on their value, lowest value first.
|
|
% We scan through the primary tags values from zero to maximum.
|
|
% Note that it is not an error for a primary tag to have no case list,
|
|
% since this can happen in semideterministic switches.
|
|
|
|
:- pred tag_switch__order_ptags_by_value(int, int,
|
|
ptag_case_map, ptag_case_list).
|
|
:- mode tag_switch__order_ptags_by_value(in, in, in, out) is det.
|
|
|
|
tag_switch__order_ptags_by_value(Ptag, MaxPtag, PtagCaseMap0, PtagCaseList) :-
|
|
( MaxPtag >= Ptag ->
|
|
NextPtag is Ptag + 1,
|
|
( map__search(PtagCaseMap0, Ptag, PtagCase) ->
|
|
map__delete(PtagCaseMap0, Ptag, PtagCaseMap1),
|
|
tag_switch__order_ptags_by_value(NextPtag, MaxPtag,
|
|
PtagCaseMap1, PtagCaseList1),
|
|
PtagCaseList = [Ptag - PtagCase | PtagCaseList1]
|
|
;
|
|
tag_switch__order_ptags_by_value(NextPtag, MaxPtag,
|
|
PtagCaseMap0, PtagCaseList)
|
|
)
|
|
;
|
|
( map__is_empty(PtagCaseMap0) ->
|
|
PtagCaseList = []
|
|
;
|
|
error("PtagCaseMap0 is not empty in order_ptags_by_value")
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred tag_switch__cons_list_to_tag_list(assoc_list(cons_id, cons_tag),
|
|
list(cons_tag)).
|
|
:- mode tag_switch__cons_list_to_tag_list(in, out) is det.
|
|
|
|
tag_switch__cons_list_to_tag_list([], []).
|
|
tag_switch__cons_list_to_tag_list([_ConsId - ConsTag | ConsList],
|
|
[ConsTag | Tagslist]) :-
|
|
tag_switch__cons_list_to_tag_list(ConsList, Tagslist).
|