Files
mercury/compiler/tag_switch.m
Zoltan Somogyi cbe2ebf496 Implement multi-arm switches for the MLDS backend.
Estimated hours taken: 24
Branches: main

Implement multi-arm switches for the MLDS backend. When possible, the code of a
switch arm that has more than one cons_id is included in the output only once,
though of course with a condition that causes it to be executed for any of its
matching cons_ids. However, in the case of a tag switch in which a switch arm
has cons_ids with different primary tags, at least one of which has a
secondary tag that requires a switch on *it*, we duplicate the MLDS code of the
switch arm (we generate one copy for each such primary tag).

The diff yields a speedup of 0.4% on speedtest and a 0.5% reduction in code
size, but the main reason for it is so that programmers don't have any
incentive anymore to prefer an if-then-else chain to a switch for code
that is logically a switch with a default case that applies to many cons_ids.

compiler/handle_options.m:
	Keep disabling multi-arm switches for non-C MLDS backends, but stop
	disabling it for the C MLDS backend.

compiler/hlds_goal.m:
	Add a case number to the tagged_case type. This is to allow us to
	create maps that effectively function as maps from pieces of code
	(the code generated for the goal in the tagged case) to other things
	(various forms of the switch conditions in which that code applies)
	without making the code itself the key in the map; we can use the
	code's associated case number as the key instead.

compiler/mlds.m:
	Change the representation of the match conditions of a switch arm
	from just a simple list of conditions to a first condition and a list
	of other conditions. This enforces the invariant that the switch arm
	must always apply in at least one condition.

compiler/ml_string_switch.m:
compiler/ml_switch_gen.m:
compiler/ml_tag_switch.m:
	Implement the above.

	In ml_switch_gen.m, change the structure of the predicate that decides
	which code generation scheme to employ from an if-then-else chain
	to being basically a switch. This structure, which is modelled on the
	one used in the LLDS code generator, should be significantly clearer.

	As part of this change of structure, sort the cases by the cost of the
	tag tests only if the chosen code generation wants the cases sorted in
	this way.

compiler/switch_util.m:
	Add facilities for grouping together primary tags that both have
	exactly the same code, so that we don't have to duplicate its code.
	This is possible only when neither primary tag is shared by more
	than one cons_id. This grouping benefits even the LLDS backend.

	This involved separating out the types and predicates that are intended
	for use in compilation schemes in which more than one switch value can
	share the same piece of code (such as switches or if-then-else chains
	in C) from those intended for use in compilation in which this is not
	possible (such as lookup tables).

	Replace several uses of pairs with named types and function symbols.

	Delete a predicate that isn't needed anymore.

	Add a predicate to support the fix to switch_gen.m.

compiler/switch_gen.m:
	Fix an old oversight. When I added multi-arm switches for the LLDS
	backend, I did not update the test for whether the switch is big enough
	for each kind of nontrivial indexing to count cons_ids rather than
	switch arms (the benefit of smart indexing is proportional to the
	former). This diff fixes that.

compiler/ml_unify_gen.m:
	Change the interface of some predicates to make them more useful
	for generating code for switches.

compiler/tag_switch.m:
	Conform to the changes in switch_util.m.

compiler/ml_simplify_switch.m:
	Conform to the changes above.

	Rename some predicates to better reflect their purpose.

compiler/dense_switch.m:
compiler/lookup_switch.m:
compiler/ml_elim_nested.m:
compiler/ml_optimize.m:
compiler/ml_tailcall.m:
compiler/ml_util.m:
compiler/mlds_to_c.m:
compiler/mlds_to_gcc.m:
compiler/mlds_to_il.m:
compiler/mlds_to_java.m:
compiler/switch_case.m:
	Conform to the changes above.

tests/hard_coded/multi_arm_switch_2.{m,exp}:
tests/hard_coded/string_switch.{m,exp}:
	New test cases to exercise the new functionality.

tests/hard_coded/Mmakefile:
	Enable the new tests.
2009-08-25 23:47:00 +00:00

1026 lines
42 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1994-2000,2002-2007, 2009 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.
%-----------------------------------------------------------------------------%
%
% File: tag_switch.m.
% Author: zs.
%
% Generate switches based on primary and secondary tags.
%
%-----------------------------------------------------------------------------%
:- module ll_backend.tag_switch.
:- interface.
:- import_module hlds.code_model.
:- import_module hlds.hlds_goal.
:- import_module ll_backend.code_info.
:- import_module ll_backend.llds.
:- import_module parse_tree.prog_data.
:- import_module list.
%-----------------------------------------------------------------------------%
% Generate intelligent indexing code for tag based switches.
%
:- pred generate_tag_switch(list(tagged_case)::in, rval::in, mer_type::in,
string::in, code_model::in, can_fail::in, hlds_goal_info::in, label::in,
branch_end::in, branch_end::out, llds_code::out,
code_info::in, code_info::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module backend_libs.builtin_ops.
:- import_module backend_libs.rtti.
:- import_module backend_libs.switch_util.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_llds.
:- import_module hlds.hlds_out.
:- import_module hlds.hlds_pred.
:- import_module libs.compiler_util.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module ll_backend.code_gen.
:- import_module ll_backend.switch_case.
:- import_module ll_backend.trace_gen.
:- import_module parse_tree.prog_data.
:- import_module assoc_list.
:- import_module cord.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module string.
:- import_module svmap.
%-----------------------------------------------------------------------------%
% 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 (at least not without feedback from a profiler). 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,
% tag2 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.
%-----------------------------------------------------------------------------%
generate_tag_switch(TaggedCases, VarRval, VarType, VarName, CodeModel, CanFail,
SwitchGoalInfo, EndLabel, !MaybeEnd, Code, !CI) :-
% We get registers for holding the primary and (if needed) the secondary
% tag. The tags needed only by the switch, and no other code gets control
% between producing the tag values and all their uses, so we can release
% the registers for use by the code of the various cases.
%
% We forgo using the primary tag 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.
%
% We need to get and release the registers before we generate the code
% of the switch arms, since the set of free registers will in general be
% different before and after that action.
acquire_reg(reg_r, PtagReg, !CI),
acquire_reg(reg_r, StagReg, !CI),
release_reg(PtagReg, !CI),
release_reg(StagReg, !CI),
% Group the cases based on primary tag value and find out how many
% constructors share each primary tag value.
get_module_info(!.CI, ModuleInfo),
get_ptag_counts(VarType, ModuleInfo, MaxPrimary, PtagCountMap),
remember_position(!.CI, BranchStart),
Params = represent_params(VarName, SwitchGoalInfo, CodeModel, BranchStart,
EndLabel),
group_cases_by_ptag(TaggedCases, represent_tagged_case_for_llds(Params),
map.init, CaseLabelMap0, !MaybeEnd, !CI, _, PtagCaseMap),
map.count(PtagCaseMap, PtagsUsed),
get_globals(!.CI, 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
),
(
PrimaryMethod \= jump_table,
PtagsUsed >= 2,
globals.lookup_int_option(Globals, num_real_r_regs, NumRealRegs),
(
NumRealRegs = 0
;
( PtagReg = reg(reg_r, PtagRegNo) ->
PtagRegNo =< NumRealRegs
;
unexpected(this_file, "improper reg in tag switch")
)
)
->
PtagCode = singleton(
llds_instr(assign(PtagReg, unop(tag, VarRval)),
"compute tag to switch on")
),
PtagRval = lval(PtagReg)
;
PtagCode = empty,
PtagRval = unop(tag, VarRval)
),
% We generate EndCode (and if needed, FailCode) here because the last
% case within a primary tag may not be the last case overall.
EndCode = singleton(
llds_instr(label(EndLabel), "end of tag switch")
),
(
CanFail = cannot_fail,
MaybeFailLabel = no,
FailCode = empty
;
CanFail = can_fail,
get_next_label(FailLabel, !CI),
MaybeFailLabel = yes(FailLabel),
FailLabelCode = singleton(
llds_instr(label(FailLabel), "switch has failed")
),
% We must generate the failure code in the context in which none of the
% switch arms have been executed yet.
reset_to_position(BranchStart, !CI),
generate_failure(FailureCode, !CI),
FailCode = FailLabelCode ++ FailureCode
),
(
PrimaryMethod = binary_search,
order_ptags_by_value(0, MaxPrimary, PtagCaseMap, PtagCaseList),
generate_primary_binary_search(PtagCaseList, 0, MaxPrimary, PtagRval,
StagReg, VarRval, MaybeFailLabel, PtagCountMap, CasesCode,
CaseLabelMap0, CaseLabelMap, !CI)
;
PrimaryMethod = jump_table,
order_ptags_by_value(0, MaxPrimary, PtagCaseMap, PtagCaseList),
generate_primary_jump_table(PtagCaseList, 0, MaxPrimary, StagReg,
VarRval, MaybeFailLabel, PtagCountMap, Targets, TableCode,
CaseLabelMap0, CaseLabelMap, !CI),
SwitchCode = singleton(
llds_instr(computed_goto(PtagRval, Targets),
"switch on primary tag")
),
CasesCode = SwitchCode ++ TableCode
;
PrimaryMethod = try_chain,
order_ptags_by_count(PtagCountMap, PtagCaseMap, PtagCaseList0),
(
CanFail = cannot_fail,
PtagCaseList0 = [MostFreqCase | OtherCases]
->
PtagCaseList = OtherCases ++ [MostFreqCase]
;
PtagCaseList = PtagCaseList0
),
generate_primary_try_chain(PtagCaseList, PtagRval, StagReg, VarRval,
MaybeFailLabel, PtagCountMap, empty, empty, CasesCode,
CaseLabelMap0, CaseLabelMap, !CI)
;
PrimaryMethod = try_me_else_chain,
order_ptags_by_count(PtagCountMap, PtagCaseMap, PtagCaseList),
generate_primary_try_me_else_chain(PtagCaseList, PtagRval, StagReg,
VarRval, MaybeFailLabel, PtagCountMap, CasesCode,
CaseLabelMap0, CaseLabelMap, !CI)
),
map.foldl(add_remaining_case, CaseLabelMap, empty, RemainingCasesCode),
Code = PtagCode ++ CasesCode ++ RemainingCasesCode ++ FailCode ++ EndCode.
%-----------------------------------------------------------------------------%
% Generate a switch on a primary tag value using a try-me-else chain.
%
:- pred generate_primary_try_me_else_chain(ptag_case_group_list(label)::in,
rval::in, lval::in, rval::in, maybe(label)::in,
ptag_count_map::in, llds_code::out,
case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_primary_try_me_else_chain([], _, _, _, _, _, _,
!CaseLabelMap, !CI) :-
unexpected(this_file, "generate_primary_try_me_else_chain: empty switch").
generate_primary_try_me_else_chain([PtagGroup | PtagGroups], PtagRval, StagReg,
VarRval, MaybeFailLabel, PtagCountMap, Code, !CaseLabelMap, !CI) :-
PtagGroup = ptag_case_group_entry(MainPtag, OtherPtags, PtagCase),
PtagCase = ptag_case(StagLoc, StagGoalMap),
map.lookup(PtagCountMap, MainPtag, CountInfo),
CountInfo = StagLocPrime - MaxSecondary,
expect(unify(StagLoc, StagLocPrime), this_file,
"generate_primary_try_me_else_chain: secondary tag locations differ"),
(
PtagGroups = [_ | _],
generate_primary_try_me_else_chain_case(PtagRval, StagReg,
MainPtag, OtherPtags, PtagCase, MaxSecondary, VarRval,
MaybeFailLabel, ThisTagCode, !CaseLabelMap, !CI),
generate_primary_try_me_else_chain(PtagGroups, PtagRval, StagReg,
VarRval, MaybeFailLabel, PtagCountMap, OtherTagsCode,
!CaseLabelMap, !CI),
Code = ThisTagCode ++ OtherTagsCode
;
PtagGroups = [],
(
MaybeFailLabel = yes(FailLabel),
generate_primary_try_me_else_chain_case(PtagRval, StagReg,
MainPtag, OtherPtags, PtagCase, MaxSecondary, VarRval,
MaybeFailLabel, ThisTagCode, !CaseLabelMap, !CI),
% 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 = singleton(
llds_instr(goto(code_label(FailLabel)),
"primary tag with no code to handle it")
),
Code = ThisTagCode ++ FailCode
;
MaybeFailLabel = no,
generate_primary_tag_code(StagGoalMap, MainPtag, OtherPtags,
MaxSecondary, StagReg, StagLoc, VarRval, MaybeFailLabel, Code,
!CaseLabelMap, !CI)
)
).
:- pred generate_primary_try_me_else_chain_case(rval::in, lval::in,
tag_bits::in, list(tag_bits)::in, ptag_case(label)::in, int::in, rval::in,
maybe(label)::in, llds_code::out, case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_primary_try_me_else_chain_case(PtagRval, StagReg,
MainPtag, OtherPtags, PtagCase, MaxSecondary, VarRval, MaybeFailLabel,
Code, !CaseLabelMap, !CI) :-
get_next_label(ElseLabel, !CI),
TestRval0 = binop(ne, PtagRval,
unop(mktag, const(llconst_int(MainPtag)))),
generate_primary_try_me_else_chain_other_ptags(OtherPtags, PtagRval,
TestRval0, TestRval),
TestCode = singleton(
llds_instr(if_val(TestRval, code_label(ElseLabel)),
"test primary tag only")
),
PtagCase = ptag_case(StagLoc, StagGoalMap),
generate_primary_tag_code(StagGoalMap, MainPtag, OtherPtags, MaxSecondary,
StagReg, StagLoc, VarRval, MaybeFailLabel, TagCode,
!CaseLabelMap, !CI),
ElseCode = singleton(
llds_instr(label(ElseLabel), "handle next primary tag")
),
Code = TestCode ++ TagCode ++ ElseCode.
:- pred generate_primary_try_me_else_chain_other_ptags(list(tag_bits)::in,
rval::in, rval::in, rval::out) is det.
generate_primary_try_me_else_chain_other_ptags([], _, TestRval, TestRval).
generate_primary_try_me_else_chain_other_ptags([OtherPtag | OtherPtags],
PtagRval, TestRval0, TestRval) :-
ThisTestRval = binop(ne, PtagRval,
unop(mktag, const(llconst_int(OtherPtag)))),
TestRval1 = binop(logical_and, TestRval0, ThisTestRval),
generate_primary_try_me_else_chain_other_ptags(OtherPtags,
PtagRval, TestRval1, TestRval).
%-----------------------------------------------------------------------------%
% Generate a switch on a primary tag value using a try chain.
%
:- pred generate_primary_try_chain(ptag_case_group_list(label)::in,
rval::in, lval::in, rval::in, maybe(label)::in,
ptag_count_map::in, llds_code::in, llds_code::in, llds_code::out,
case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_primary_try_chain([], _, _, _, _, _, _, _, _, !CaseLabelMap, !CI) :-
unexpected(this_file, "empty list in generate_primary_try_chain").
generate_primary_try_chain([PtagGroup | PtagGroups], PtagRval, StagReg,
VarRval, MaybeFailLabel, PtagCountMap, PrevTestsCode0, PrevCasesCode0,
Code, !CaseLabelMap, !CI) :-
PtagGroup = ptag_case_group_entry(MainPtag, OtherPtags, PtagCase),
PtagCase = ptag_case(StagLoc, StagGoalMap),
map.lookup(PtagCountMap, MainPtag, CountInfo),
CountInfo = StagLocPrime - MaxSecondary,
expect(unify(StagLoc, StagLocPrime), this_file,
"secondary tag locations differ in generate_primary_try_chain"),
(
PtagGroups = [_ | _],
generate_primary_try_chain_case(PtagRval, StagReg,
MainPtag, OtherPtags, PtagCase, MaxSecondary, VarRval,
MaybeFailLabel,
PrevTestsCode0, PrevTestsCode1, PrevCasesCode0, PrevCasesCode1,
!CaseLabelMap, !CI),
generate_primary_try_chain(PtagGroups, PtagRval, StagReg, VarRval,
MaybeFailLabel, PtagCountMap, PrevTestsCode1, PrevCasesCode1,
Code, !CaseLabelMap, !CI)
;
PtagGroups = [],
(
MaybeFailLabel = yes(FailLabel),
generate_primary_try_chain_case(PtagRval, StagReg,
MainPtag, OtherPtags, PtagCase, MaxSecondary,
VarRval, MaybeFailLabel,
PrevTestsCode0, PrevTestsCode1, PrevCasesCode0, PrevCasesCode1,
!CaseLabelMap, !CI),
FailCode = singleton(
llds_instr(goto(code_label(FailLabel)),
"primary tag with no code to handle it")
),
Code = PrevTestsCode1 ++ FailCode ++ PrevCasesCode1
;
MaybeFailLabel = no,
make_ptag_comment("fallthrough to last primary tag value: ",
MainPtag, OtherPtags, Comment),
CommentCode = singleton(
llds_instr(comment(Comment), "")
),
generate_primary_tag_code(StagGoalMap, MainPtag, OtherPtags,
MaxSecondary, StagReg, StagLoc, VarRval, MaybeFailLabel,
TagCode, !CaseLabelMap, !CI),
Code = PrevTestsCode0 ++ CommentCode ++ TagCode ++ PrevCasesCode0
)
).
:- pred generate_primary_try_chain_case(rval::in, lval::in,
tag_bits::in, list(tag_bits)::in,
ptag_case(label)::in, int::in, rval::in, maybe(label)::in,
llds_code::in, llds_code::out, llds_code::in, llds_code::out,
case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_primary_try_chain_case(PtagRval, StagReg, MainPtag, OtherPtags,
PtagCase, MaxSecondary, VarRval, MaybeFailLabel,
PrevTestsCode0, PrevTestsCode, PrevCasesCode0, PrevCasesCode,
!CaseLabelMap, !CI) :-
get_next_label(ThisPtagLabel, !CI),
TestRval0 = binop(eq, PtagRval,
unop(mktag, const(llconst_int(MainPtag)))),
generate_primary_try_chain_other_ptags(OtherPtags, PtagRval,
TestRval0, TestRval),
TestCode = singleton(
llds_instr(if_val(TestRval, code_label(ThisPtagLabel)),
"test primary tag only")
),
make_ptag_comment("primary tag value: ", MainPtag, OtherPtags, Comment),
LabelCode = singleton(
llds_instr(label(ThisPtagLabel), Comment)
),
PtagCase = ptag_case(StagLoc, StagGoalMap),
generate_primary_tag_code(StagGoalMap, MainPtag, OtherPtags, MaxSecondary,
StagReg, StagLoc, VarRval, MaybeFailLabel, TagCode,
!CaseLabelMap, !CI),
PrevTestsCode = PrevTestsCode0 ++ TestCode,
PrevCasesCode = LabelCode ++ TagCode ++ PrevCasesCode0.
:- pred generate_primary_try_chain_other_ptags(list(tag_bits)::in,
rval::in, rval::in, rval::out) is det.
generate_primary_try_chain_other_ptags([], _, TestRval, TestRval).
generate_primary_try_chain_other_ptags([OtherPtag | OtherPtags],
PtagRval, TestRval0, TestRval) :-
ThisTestRval = binop(eq, PtagRval,
unop(mktag, const(llconst_int(OtherPtag)))),
TestRval1 = binop(logical_or, TestRval0, ThisTestRval),
generate_primary_try_chain_other_ptags(OtherPtags,
PtagRval, TestRval1, TestRval).
%-----------------------------------------------------------------------------%
% Generate the cases for a primary tag using a dense jump table
% that has an entry for all possible primary tag values.
%
:- pred generate_primary_jump_table(ptag_case_list(label)::in, int::in,
int::in, lval::in, rval::in, maybe(label)::in, ptag_count_map::in,
list(maybe(label))::out, llds_code::out,
case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_primary_jump_table(PtagGroups, CurPrimary, MaxPrimary, StagReg,
VarRval, MaybeFailLabel, PtagCountMap, Targets, Code,
!CaseLabelMap, !CI) :-
( CurPrimary > MaxPrimary ->
expect(unify(PtagGroups, []), this_file,
"generate_primary_jump_table: PtagGroups != [] when Cur > Max"),
Targets = [],
Code = empty
;
NextPrimary = CurPrimary + 1,
(
PtagGroups = [PtagCaseEntry | PtagGroupsTail],
PtagCaseEntry = ptag_case_entry(CurPrimary, PrimaryInfo)
->
PrimaryInfo = ptag_case(StagLoc, StagGoalMap),
map.lookup(PtagCountMap, CurPrimary, CountInfo),
CountInfo = StagLocPrime - MaxSecondary,
expect(unify(StagLoc, StagLocPrime), this_file,
"secondary tag locations differ " ++
"in generate_primary_jump_table"),
get_next_label(NewLabel, !CI),
Comment = "start of a case in primary tag switch: ptag " ++
string.int_to_string(CurPrimary),
LabelCode = singleton(llds_instr(label(NewLabel), Comment)),
generate_primary_tag_code(StagGoalMap, CurPrimary, [],
MaxSecondary, StagReg, StagLoc, VarRval, MaybeFailLabel,
ThisTagCode, !CaseLabelMap, !CI),
generate_primary_jump_table(PtagGroupsTail, NextPrimary,
MaxPrimary, StagReg, VarRval, MaybeFailLabel, PtagCountMap,
TailTargets, TailCode, !CaseLabelMap, !CI),
Targets = [yes(NewLabel) | TailTargets],
Code = LabelCode ++ ThisTagCode ++ TailCode
;
generate_primary_jump_table(PtagGroups, NextPrimary, MaxPrimary,
StagReg, VarRval, MaybeFailLabel, PtagCountMap,
TailTargets, TailCode, !CaseLabelMap, !CI),
Targets = [MaybeFailLabel | TailTargets],
Code = TailCode
)
).
%-----------------------------------------------------------------------------%
% 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 generate_primary_binary_search(ptag_case_list(label)::in, int::in,
int::in, rval::in, lval::in, rval::in, maybe(label)::in,
ptag_count_map::in, llds_code::out,
case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_primary_binary_search(PtagGroups, MinPtag, MaxPtag, PtagRval, StagReg,
VarRval, MaybeFailLabel, PtagCountMap, Code, !CaseLabelMap, !CI) :-
( MinPtag = MaxPtag ->
CurPrimary = MinPtag,
(
PtagGroups = [],
% There is no code for this tag.
(
MaybeFailLabel = yes(FailLabel),
string.int_to_string(CurPrimary, PtagStr),
Comment = "no code for ptag " ++ PtagStr,
Code = singleton(
llds_instr(goto(code_label(FailLabel)), Comment)
)
;
MaybeFailLabel = no,
% The switch is cannot_fail, which means this case cannot
% happen.
Code = empty
)
;
PtagGroups = [ptag_case_entry(CurPrimaryPrime, PrimaryInfo)],
expect(unify(CurPrimary, CurPrimaryPrime), this_file,
"generate_primary_binary_search: cur_primary mismatch"),
PrimaryInfo = ptag_case(StagLoc, StagGoalMap),
map.lookup(PtagCountMap, CurPrimary, CountInfo),
CountInfo = StagLocPrime - MaxSecondary,
expect(unify(StagLoc, StagLocPrime), this_file,
"generate_primary_jump_table: secondary tag locations differ"),
generate_primary_tag_code(StagGoalMap, CurPrimary, [],
MaxSecondary, StagReg, StagLoc, VarRval, MaybeFailLabel, Code,
!CaseLabelMap, !CI)
;
PtagGroups = [_, _ | _],
unexpected(this_file,
"caselist not singleton or empty when binary search ends")
)
;
LowRangeEnd = (MinPtag + MaxPtag) // 2,
HighRangeStart = LowRangeEnd + 1,
InLowGroup = (pred(PtagGroup::in) is semidet :-
PtagGroup = ptag_case_entry(Ptag, _),
Ptag =< LowRangeEnd
),
list.filter(InLowGroup, PtagGroups, LowGroups, HighGroups),
get_next_label(NewLabel, !CI),
string.int_to_string(MinPtag, LowStartStr),
string.int_to_string(LowRangeEnd, LowEndStr),
string.int_to_string(HighRangeStart, HighStartStr),
string.int_to_string(MaxPtag, HighEndStr),
IfComment = "fallthrough for ptags " ++
LowStartStr ++ " to " ++ LowEndStr,
LabelComment = "code for ptags " ++
HighStartStr ++ " to " ++ HighEndStr,
LowRangeEndConst = const(llconst_int(LowRangeEnd)),
TestRval = binop(int_gt, PtagRval, LowRangeEndConst),
IfCode = singleton(
llds_instr(if_val(TestRval, code_label(NewLabel)), IfComment)
),
LabelCode = singleton(
llds_instr(label(NewLabel), LabelComment)
),
generate_primary_binary_search(LowGroups, MinPtag, LowRangeEnd,
PtagRval, StagReg, VarRval, MaybeFailLabel, PtagCountMap,
LowRangeCode, !CaseLabelMap, !CI),
generate_primary_binary_search(HighGroups, HighRangeStart, MaxPtag,
PtagRval, StagReg, VarRval, MaybeFailLabel, PtagCountMap,
HighRangeCode, !CaseLabelMap, !CI),
Code = IfCode ++ LowRangeCode ++ 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 generate_primary_tag_code(stag_goal_map(label)::in,
tag_bits::in, list(tag_bits)::in, int::in, lval::in, sectag_locn::in,
rval::in, maybe(label)::in, llds_code::out,
case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_primary_tag_code(StagGoalMap, MainPtag, OtherPtags, MaxSecondary,
StagReg, StagLoc, Rval, MaybeFailLabel, Code, !CaseLabelMap, !CI) :-
map.to_assoc_list(StagGoalMap, StagGoalList),
(
StagLoc = sectag_none,
% There is no secondary tag, so there is no switch on it.
(
StagGoalList = [],
unexpected(this_file, "no goal for non-shared tag")
;
StagGoalList = [StagGoal],
( StagGoal = -1 - CaseLabel ->
generate_case_code_or_jump(CaseLabel, Code, !CaseLabelMap)
;
unexpected(this_file, "badly formed goal for non-shared tag")
)
;
StagGoalList = [_, _ | _],
unexpected(this_file, "more than one goal for non-shared tag")
)
;
( StagLoc = sectag_local
; StagLoc = sectag_remote
),
expect(unify(OtherPtags, []), this_file, ">1 ptag with secondary tag"),
% There is a secondary tag, so figure out how to switch on it.
get_globals(!.CI, 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 = sectag_remote,
OrigStagRval = lval(field(yes(MainPtag), Rval,
const(llconst_int(0)))),
Comment = "compute remote sec tag to switch on"
;
StagLoc = sectag_local,
OrigStagRval = unop(unmkbody, Rval),
Comment = "compute local sec tag to switch on"
),
(
SecondaryMethod \= jump_table,
MaxSecondary >= 2,
globals.lookup_int_option(Globals, num_real_r_regs, NumRealRegs),
(
NumRealRegs = 0
;
( StagReg = reg(reg_r, StagRegNo) ->
StagRegNo =< NumRealRegs
;
unexpected(this_file, "improper reg in tag switch")
)
)
->
StagCode = singleton(
llds_instr(assign(StagReg, OrigStagRval), Comment)
),
StagRval = lval(StagReg)
;
StagCode = empty,
StagRval = OrigStagRval
),
(
MaybeFailLabel = yes(FailLabel),
(
list.length(StagGoalList, StagGoalCount),
FullGoalCount = MaxSecondary + 1,
FullGoalCount = StagGoalCount
->
MaybeSecFailLabel = no
;
MaybeSecFailLabel = yes(FailLabel)
)
;
MaybeFailLabel = no,
MaybeSecFailLabel = no
),
(
SecondaryMethod = jump_table,
generate_secondary_jump_table(StagGoalList, 0, MaxSecondary,
MaybeSecFailLabel, Targets),
Code = singleton(
llds_instr(computed_goto(StagRval, Targets),
"switch on secondary tag")
)
;
SecondaryMethod = binary_search,
generate_secondary_binary_search(StagGoalList, 0, MaxSecondary,
StagRval, MaybeSecFailLabel, Code, !CaseLabelMap, !CI)
;
SecondaryMethod = try_chain,
generate_secondary_try_chain(StagGoalList, StagRval,
MaybeSecFailLabel, empty, Codes, !CaseLabelMap),
Code = StagCode ++ Codes
;
SecondaryMethod = try_me_else_chain,
generate_secondary_try_me_else_chain(StagGoalList, StagRval,
MaybeSecFailLabel, Codes, !CaseLabelMap, !CI),
Code = StagCode ++ Codes
)
).
%-----------------------------------------------------------------------------%
% Generate a switch on a secondary tag value using a try-me-else chain.
%
:- pred generate_secondary_try_me_else_chain(stag_goal_list(label)::in,
rval::in, maybe(label)::in, llds_code::out,
case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_secondary_try_me_else_chain([], _, _, _, !CaseLabelMap, !CI) :-
unexpected(this_file,
"generate_secondary_try_me_else_chain: empty switch").
generate_secondary_try_me_else_chain([Case | Cases], StagRval,
MaybeFailLabel, Code, !CaseLabelMap, !CI) :-
Case = Secondary - CaseLabel,
(
Cases = [_ | _],
generate_secondary_try_me_else_chain_case(CaseLabel, StagRval,
Secondary, ThisCode, !CaseLabelMap, !CI),
generate_secondary_try_me_else_chain(Cases, StagRval,
MaybeFailLabel, OtherCode, !CaseLabelMap, !CI),
Code = ThisCode ++ OtherCode
;
Cases = [],
(
MaybeFailLabel = yes(FailLabel),
generate_secondary_try_me_else_chain_case(CaseLabel, StagRval,
Secondary, ThisCode, !CaseLabelMap, !CI),
FailCode = singleton(
llds_instr(goto(code_label(FailLabel)),
"secondary tag does not match")
),
Code = ThisCode ++ FailCode
;
MaybeFailLabel = no,
generate_case_code_or_jump(CaseLabel, Code, !CaseLabelMap)
)
).
:- pred generate_secondary_try_me_else_chain_case(label::in, rval::in, int::in,
llds_code::out, case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_secondary_try_me_else_chain_case(CaseLabel, StagRval, Secondary,
Code, !CaseLabelMap, !CI) :-
generate_case_code_or_jump(CaseLabel, CaseCode, !CaseLabelMap),
% XXX Optimize what we generate when CaseCode = goto(CaseLabel).
get_next_label(ElseLabel, !CI),
TestCode = singleton(
llds_instr(
if_val(binop(ne, StagRval, const(llconst_int(Secondary))),
code_label(ElseLabel)),
"test remote sec tag only")
),
ElseLabelCode = singleton(
llds_instr(label(ElseLabel), "handle next secondary tag")
),
Code = TestCode ++ CaseCode ++ ElseLabelCode.
%-----------------------------------------------------------------------------%
% Generate a switch on a secondary tag value using a try chain.
%
:- pred generate_secondary_try_chain(stag_goal_list(label)::in, rval::in,
maybe(label)::in, llds_code::in, llds_code::out,
case_label_map::in, case_label_map::out) is det.
generate_secondary_try_chain([], _, _, _, _, !CaseLabelMap) :-
unexpected(this_file, "generate_secondary_try_chain: empty switch").
generate_secondary_try_chain([Case | Cases], StagRval, MaybeFailLabel,
PrevTestsCode0, Code, !CaseLabelMap) :-
Case = Secondary - CaseLabel,
(
Cases = [_ | _],
generate_secondary_try_chain_case(CaseLabel, StagRval, Secondary,
PrevTestsCode0, PrevTestsCode1, !.CaseLabelMap),
generate_secondary_try_chain(Cases, StagRval,
MaybeFailLabel, PrevTestsCode1, Code, !CaseLabelMap)
;
Cases = [],
(
MaybeFailLabel = yes(FailLabel),
generate_secondary_try_chain_case(CaseLabel, StagRval, Secondary,
PrevTestsCode0, PrevTestsCode1, !.CaseLabelMap),
FailCode = singleton(
llds_instr(goto(code_label(FailLabel)),
"secondary tag with no code to handle it")
),
Code = PrevTestsCode1 ++ FailCode
;
MaybeFailLabel = no,
generate_case_code_or_jump(CaseLabel, ThisCode, !CaseLabelMap),
Code = PrevTestsCode0 ++ ThisCode
)
).
:- pred generate_secondary_try_chain_case(label::in, rval::in, int::in,
llds_code::in, llds_code::out, case_label_map::in) is det.
generate_secondary_try_chain_case(CaseLabel, StagRval, Secondary,
PrevTestsCode0, PrevTestsCode, CaseLabelMap) :-
map.lookup(CaseLabelMap, CaseLabel, CaseInfo0),
CaseInfo0 = case_label_info(Comment, _CaseCode, _CaseGenerated),
TestCode = singleton(
llds_instr(
if_val(binop(eq, StagRval, const(llconst_int(Secondary))),
code_label(CaseLabel)),
"test remote sec tag only for " ++ Comment)
),
PrevTestsCode = PrevTestsCode0 ++ TestCode.
%-----------------------------------------------------------------------------%
% Generate the cases for a primary tag using a dense jump table
% that has an entry for all possible secondary tag values.
%
:- pred generate_secondary_jump_table(stag_goal_list(label)::in, int::in,
int::in, maybe(label)::in, list(maybe(label))::out) is det.
generate_secondary_jump_table(CaseList, CurSecondary, MaxSecondary,
MaybeFailLabel, Targets) :-
( CurSecondary > MaxSecondary ->
expect(unify(CaseList, []), this_file,
"caselist not empty when reaching limiting secondary tag"),
Targets = []
;
NextSecondary = CurSecondary + 1,
( CaseList = [CurSecondary - CaseLabel | CaseListTail] ->
generate_secondary_jump_table(CaseListTail, NextSecondary,
MaxSecondary, MaybeFailLabel, OtherTargets),
Targets = [yes(CaseLabel) | OtherTargets]
;
generate_secondary_jump_table(CaseList, NextSecondary,
MaxSecondary, MaybeFailLabel, OtherTargets),
Targets = [MaybeFailLabel | OtherTargets]
)
).
%-----------------------------------------------------------------------------%
% 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 generate_secondary_binary_search(stag_goal_list(label)::in,
int::in, int::in, rval::in, maybe(label)::in, llds_code::out,
case_label_map::in, case_label_map::out,
code_info::in, code_info::out) is det.
generate_secondary_binary_search(StagGoals, MinStag, MaxStag, StagRval,
MaybeFailLabel, Code, !CaseLabelMap, !CI) :-
( MinStag = MaxStag ->
CurSec = MinStag,
(
StagGoals = [],
% There is no code for this tag.
(
MaybeFailLabel = yes(FailLabel),
string.int_to_string(CurSec, StagStr),
Comment = "no code for ptag " ++ StagStr,
Code = singleton(
llds_instr(goto(code_label(FailLabel)), Comment)
)
;
MaybeFailLabel = no,
Code = empty
)
;
StagGoals = [CurSecPrime - CaseLabel],
expect(unify(CurSec, CurSecPrime), this_file,
"generate_secondary_binary_search: cur_secondary mismatch"),
generate_case_code_or_jump(CaseLabel, Code, !CaseLabelMap)
;
StagGoals = [_, _ | _],
unexpected(this_file,
"generate_secondary_binary_search: " ++
"goallist not singleton or empty when binary search ends")
)
;
LowRangeEnd = (MinStag + MaxStag) // 2,
HighRangeStart = LowRangeEnd + 1,
InLowGroup = (pred(StagGoal::in) is semidet :-
StagGoal = Stag - _,
Stag =< LowRangeEnd
),
list.filter(InLowGroup, StagGoals, LowGoals, HighGoals),
get_next_label(NewLabel, !CI),
string.int_to_string(MinStag, LowStartStr),
string.int_to_string(LowRangeEnd, LowEndStr),
string.int_to_string(HighRangeStart, HighStartStr),
string.int_to_string(MaxStag, HighEndStr),
IfComment = "fallthrough for stags " ++
LowStartStr ++ " to " ++ LowEndStr,
LabelComment = "code for stags " ++
HighStartStr ++ " to " ++ HighEndStr,
LowRangeEndConst = const(llconst_int(LowRangeEnd)),
TestRval = binop(int_gt, StagRval, LowRangeEndConst),
IfCode = singleton(
llds_instr(if_val(TestRval, code_label(NewLabel)), IfComment)
),
LabelCode = singleton(
llds_instr(label(NewLabel), LabelComment)
),
generate_secondary_binary_search(LowGoals, MinStag, LowRangeEnd,
StagRval, MaybeFailLabel, LowRangeCode, !CaseLabelMap, !CI),
generate_secondary_binary_search(HighGoals, HighRangeStart, MaxStag,
StagRval, MaybeFailLabel, HighRangeCode, !CaseLabelMap, !CI),
Code = IfCode ++ LowRangeCode ++ LabelCode ++ HighRangeCode
).
%-----------------------------------------------------------------------------%
:- pred make_ptag_comment(string::in, tag_bits::in, list(tag_bits)::in,
string::out) is det.
make_ptag_comment(BaseStr, MainPtag, OtherPtags, Comment) :-
(
OtherPtags = [],
Comment = BaseStr ++ string.int_to_string(MainPtag)
;
OtherPtags = [_ | _],
Comment = BaseStr ++ string.int_to_string(MainPtag)
++ "(shared with " ++
string.join_list(", ", list.map(string.int_to_string, OtherPtags))
++ ")"
).
%-----------------------------------------------------------------------------%
:- func this_file = string.
this_file = "tag_switch.m".
%-----------------------------------------------------------------------------%