Files
mercury/compiler/dense_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

293 lines
11 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1994-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: dense_switch.m.
% Author: fjh.
%
% For switches on atomic types, generate code using a dense jump table.
%
%-----------------------------------------------------------------------------%
:- module ll_backend.dense_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.
%-----------------------------------------------------------------------------%
:- type dense_switch_info.
% Should this switch be implemented as a dense jump table?
% If so, we return the starting and ending values for the table,
% and whether the switch is not covers all cases or not
% (we may convert locally semidet switches into locally det
% switches by adding extra cases whose body is just `fail').
%
:- pred tagged_case_list_is_dense_switch(code_info::in, mer_type::in,
list(tagged_case)::in, int::in, int::in, int::in, int::in,
can_fail::in, dense_switch_info::out) is semidet.
% Generate code for a switch using a dense jump table.
%
:- pred generate_dense_switch(list(tagged_case)::in, rval::in, string::in,
code_model::in, hlds_goal_info::in, dense_switch_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.switch_util.
:- import_module check_hlds.type_util.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_llds.
:- import_module hlds.hlds_out.
:- import_module libs.compiler_util.
:- import_module ll_backend.code_gen.
:- import_module ll_backend.trace_gen.
:- import_module parse_tree.prog_type.
:- import_module assoc_list.
:- import_module cord.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module svmap.
%-----------------------------------------------------------------------------%
:- type dense_switch_info
---> dense_switch_info(
first_value :: int,
last_value :: int,
new_can_fail :: can_fail
).
tagged_case_list_is_dense_switch(CI, VarType, TaggedCases,
LowerLimit, UpperLimit, NumValues, ReqDensity, CanFail0,
DenseSwitchInfo) :-
list.length(TaggedCases, NumCases),
NumCases > 2,
Span = UpperLimit - LowerLimit,
Range = Span + 1,
Density = switch_density(NumValues, Range),
Density > ReqDensity,
(
CanFail0 = can_fail,
% For semidet switches, we normally need to check that the variable
% is in range before we index into the jump table. However, if the
% range of the type is sufficiently small, we can make the jump table
% large enough to hold all of the values for the type.
get_module_info(CI, ModuleInfo),
classify_type(ModuleInfo, VarType) = TypeCategory,
(
type_range(ModuleInfo, TypeCategory, VarType, _Min, _Max,
TypeRange),
DetDensity = switch_density(NumValues, TypeRange),
DetDensity > ReqDensity
->
CanFail = cannot_fail,
FirstVal = 0,
LastVal = TypeRange - 1
;
CanFail = CanFail0,
FirstVal = LowerLimit,
LastVal = UpperLimit
)
;
CanFail0 = cannot_fail,
CanFail = cannot_fail,
FirstVal = LowerLimit,
LastVal = UpperLimit
),
DenseSwitchInfo = dense_switch_info(FirstVal, LastVal, CanFail).
%---------------------------------------------------------------------------%
generate_dense_switch(TaggedCases, VarRval, VarName, CodeModel, SwitchGoalInfo,
DenseSwitchInfo, EndLabel, MaybeEnd0, MaybeEnd, Code, !CI) :-
% Evaluate the variable which we are going to be switching on.
% If the case values start at some number other than 0,
% then subtract that number to give us a zero-based index.
DenseSwitchInfo = dense_switch_info(FirstVal, LastVal, CanFail),
( FirstVal = 0 ->
IndexRval = VarRval
;
IndexRval = binop(int_sub, VarRval, const(llconst_int(FirstVal)))
),
% If the switch is not locally deterministic, we need to check that
% the value of the variable lies within the appropriate range.
(
CanFail = can_fail,
Difference = LastVal - FirstVal,
fail_if_rval_is_false(
binop(unsigned_le, IndexRval, const(llconst_int(Difference))),
RangeCheckCode, !CI)
;
CanFail = cannot_fail,
RangeCheckCode = empty
),
% Generate the cases.
% We keep track of the code_info at the end of the non-fail cases.
% We have to do this because generating a `fail' slot last would yield
% the wrong liveness and would not unset the failure continuation
% for a nondet switch.
list.map_foldl3(generate_dense_case(VarName, CodeModel, SwitchGoalInfo,
EndLabel), TaggedCases, CasesCodes,
map.init, IndexMap, MaybeEnd0, MaybeEnd, !CI),
CasesCode = cord_list_to_cord(CasesCodes),
% Generate the jump table.
map.to_assoc_list(IndexMap, IndexPairs),
generate_dense_jump_table(FirstVal, LastVal, IndexPairs, Targets,
no, MaybeFailLabel, !CI),
JumpCode = singleton(
llds_instr(computed_goto(IndexRval, Targets),
"switch (using dense jump table)")
),
% If there is no case for any index value in range, generate the failure
% code we execute for such cases.
(
MaybeFailLabel = no,
FailCode = empty
;
MaybeFailLabel = yes(FailLabel),
FailComment = "compiler-introduced `fail' case of dense switch",
FailLabelCode = singleton(
llds_instr(label(FailLabel), FailComment)
),
generate_failure(FailureCode, !CI),
FailCode = FailLabelCode ++ FailureCode
),
EndLabelCode = singleton(
llds_instr(label(EndLabel), "end of dense switch")
),
% Assemble the code fragments.
Code = RangeCheckCode ++ JumpCode ++ CasesCode ++ FailCode ++ EndLabelCode.
%---------------------------------------------------------------------------%
:- pred generate_dense_case(string::in, code_model::in, hlds_goal_info::in,
label::in, tagged_case::in, llds_code::out,
map(int, label)::in, map(int, label)::out,
branch_end::in, branch_end::out,
code_info::in, code_info::out) is det.
generate_dense_case(VarName, CodeModel, SwitchGoalInfo, EndLabel,
TaggedCase, Code, !IndexMap, !MaybeEnd, !CI) :-
TaggedCase = tagged_case(TaggedMainConsId, TaggedOtherConsIds, _, Goal),
project_cons_name_and_tag(TaggedMainConsId, MainConsName, MainConsTag),
list.map2(project_cons_name_and_tag, TaggedOtherConsIds,
OtherConsNames, OtherConsTags),
LabelComment = case_comment(VarName, MainConsName, OtherConsNames),
get_next_label(Label, !CI),
record_dense_label_for_cons_tag(Label, MainConsTag, !IndexMap),
list.foldl(record_dense_label_for_cons_tag(Label), OtherConsTags,
!IndexMap),
LabelCode = singleton(
llds_instr(label(Label), LabelComment)
),
% We need to save the expression cache, etc.,
% and restore them when we've finished.
remember_position(!.CI, BranchStart),
maybe_generate_internal_event_code(Goal, SwitchGoalInfo, TraceCode, !CI),
code_gen.generate_goal(CodeModel, Goal, GoalCode, !CI),
BranchToEndCode = singleton(
llds_instr(goto(code_label(EndLabel)),
"branch to end of dense switch")
),
goal_info_get_store_map(SwitchGoalInfo, StoreMap),
generate_branch_end(StoreMap, !MaybeEnd, SaveCode, !CI),
Code = LabelCode ++ TraceCode ++ GoalCode ++ SaveCode ++ BranchToEndCode,
reset_to_position(BranchStart, !CI).
:- pred record_dense_label_for_cons_tag(label::in, cons_tag::in,
map(int, label)::in, map(int, label)::out) is det.
record_dense_label_for_cons_tag(Label, ConsTag, !IndexMap) :-
( ConsTag = int_tag(Index) ->
svmap.det_insert(Index, Label, !IndexMap)
;
unexpected(this_file, "record_label_for_index: not int_tag")
).
%----------------------------------------------------------------------------%
:- pred generate_dense_jump_table(int::in, int::in,
assoc_list(int, label)::in, list(maybe(label))::out,
maybe(label)::in, maybe(label)::out,
code_info::in, code_info::out) is det.
generate_dense_jump_table(CurVal, LastVal, IndexPairs, Targets,
!MaybeFailLabel, !CI) :-
( CurVal > LastVal ->
expect(unify(IndexPairs, []), this_file,
"generate_dense_jump_table: NextVal > LastVal, IndexList not []"),
Targets = []
;
NextVal = CurVal + 1,
(
IndexPairs = [],
get_dense_fail_label(FailLabel, !MaybeFailLabel, !CI),
generate_dense_jump_table(NextVal, LastVal, IndexPairs,
LaterTargets, !MaybeFailLabel, !CI),
Targets = [yes(FailLabel) | LaterTargets]
;
IndexPairs = [FirstIndexPair | LaterIndexPairs],
FirstIndexPair = FirstIndex - FirstLabel,
( FirstIndex = CurVal ->
generate_dense_jump_table(NextVal, LastVal, LaterIndexPairs,
LaterTargets, !MaybeFailLabel, !CI),
Targets = [yes(FirstLabel) | LaterTargets]
;
get_dense_fail_label(FailLabel, !MaybeFailLabel, !CI),
generate_dense_jump_table(NextVal, LastVal, IndexPairs,
LaterTargets, !MaybeFailLabel, !CI),
Targets = [yes(FailLabel) | LaterTargets]
)
)
).
:- pred get_dense_fail_label(label::out, maybe(label)::in, maybe(label)::out,
code_info::in, code_info::out) is det.
get_dense_fail_label(FailLabel, !MaybeFailLabel, !CI) :-
(
!.MaybeFailLabel = no,
get_next_label(FailLabel, !CI),
!:MaybeFailLabel = yes(FailLabel)
;
!.MaybeFailLabel = yes(FailLabel)
).
%---------------------------------------------------------------------------%
:- func this_file = string.
this_file = "dense_switch.m".
%----------------------------------------------------------------------------%
:- end_module dense_switch.
%----------------------------------------------------------------------------%