mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 17:33:38 +00:00
compiler/switch_util.m:
Make type_range succeed for a subtype where the constructors are
assigned enum values in a discontiguous range. I checked that its
callers do not expect all values in the range to necessarily
be used by the type.
Clarify find_int_lookup_switch_params a bit.
Clarify comments.
compiler/dense_switch.m:
Use need_range_check type instead of can_fail in dense_switch_info,
as that is what the field actually means.
compiler/ml_simplify_switch.m:
Use need_range_check type instead of bool.
tests/hard_coded/dense_lookup_switch_non2.m:
Delete obsoleted XXX.
293 lines
11 KiB
Mathematica
293 lines
11 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1994-2007, 2009-2011 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.
|
|
:- import_module hlds.code_model.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module ll_backend.code_info.
|
|
:- import_module ll_backend.code_loc_dep.
|
|
:- import_module ll_backend.llds.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module list.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type dense_switch_info.
|
|
|
|
% Should this switch be implemented as a dense jump table, i.e.
|
|
% by calling generate_dense_switch below? If so, return some information
|
|
% we gathered in the process of finding that out that may prove useful
|
|
% in generate_dense_switch.
|
|
%
|
|
:- 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, code_loc_dep::in) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module backend_libs.
|
|
:- import_module backend_libs.builtin_ops.
|
|
:- import_module backend_libs.switch_util.
|
|
:- import_module check_hlds.
|
|
:- import_module check_hlds.type_util.
|
|
:- import_module hlds.hlds_data.
|
|
:- import_module hlds.hlds_llds.
|
|
:- import_module hlds.hlds_out.
|
|
:- import_module hlds.hlds_out.hlds_out_goal.
|
|
:- import_module ll_backend.code_gen.
|
|
:- import_module ll_backend.trace_gen.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module cord.
|
|
:- import_module int.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type dense_switch_info
|
|
---> dense_switch_info(
|
|
first_value :: int,
|
|
last_value :: int,
|
|
need_range_check :: need_range_check
|
|
).
|
|
|
|
tagged_case_list_is_dense_switch(CI, VarType, TaggedCases,
|
|
LowerLimit, UpperLimit, NumValues, ReqDensity, CanFail,
|
|
DenseSwitchInfo) :-
|
|
list.length(TaggedCases, NumCases),
|
|
NumCases > 2,
|
|
|
|
Span = UpperLimit - LowerLimit,
|
|
Range = Span + 1,
|
|
Density = switch_density(NumValues, Range),
|
|
Density > ReqDensity,
|
|
(
|
|
CanFail = 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,
|
|
( if
|
|
type_range(ModuleInfo, TypeCategory, VarType,
|
|
TypeMin, TypeMax, TypeRange),
|
|
DetDensity = switch_density(NumValues, TypeRange),
|
|
DetDensity > ReqDensity
|
|
then
|
|
NeedRangeCheck = dont_need_range_check,
|
|
FirstVal = TypeMin,
|
|
LastVal = TypeMax
|
|
else
|
|
NeedRangeCheck = need_range_check,
|
|
FirstVal = LowerLimit,
|
|
LastVal = UpperLimit
|
|
)
|
|
;
|
|
CanFail = cannot_fail,
|
|
NeedRangeCheck = dont_need_range_check,
|
|
FirstVal = LowerLimit,
|
|
LastVal = UpperLimit
|
|
),
|
|
DenseSwitchInfo = dense_switch_info(FirstVal, LastVal, NeedRangeCheck).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
generate_dense_switch(TaggedCases, VarRval, VarName, CodeModel, SwitchGoalInfo,
|
|
DenseSwitchInfo, EndLabel, MaybeEnd0, MaybeEnd, Code, !CI, !.CLD) :-
|
|
% 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, NeedRangeCheck),
|
|
( if FirstVal = 0 then
|
|
IndexRval = VarRval
|
|
else
|
|
IndexRval = binop(int_sub(int_type_int), VarRval,
|
|
const(llconst_int(FirstVal)))
|
|
),
|
|
% Check that the value of the variable lies within the appropriate range
|
|
% if necessary.
|
|
(
|
|
NeedRangeCheck = need_range_check,
|
|
Difference = LastVal - FirstVal,
|
|
fail_if_rval_is_false(
|
|
binop(unsigned_le, IndexRval, const(llconst_int(Difference))),
|
|
RangeCheckCode, !CI, !CLD)
|
|
;
|
|
NeedRangeCheck = dont_need_range_check,
|
|
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.
|
|
remember_position(!.CLD, BranchStart),
|
|
list.map_foldl3(
|
|
generate_dense_case(BranchStart, 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 any index value in range has no goal, then generate the failure code
|
|
% we will have to execute in 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, !.CLD),
|
|
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(position_info::in, 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(BranchStart, 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 have finished.
|
|
some [!CLD] (
|
|
reset_to_position(BranchStart, !.CI, !:CLD),
|
|
maybe_generate_internal_event_code(Goal, SwitchGoalInfo, TraceCode,
|
|
!CI, !CLD),
|
|
code_gen.generate_goal(CodeModel, Goal, GoalCode, !CI, !CLD),
|
|
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, !.CLD)
|
|
),
|
|
Code = LabelCode ++ TraceCode ++ GoalCode ++ SaveCode ++ BranchToEndCode.
|
|
|
|
:- 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) :-
|
|
( if ConsTag = int_tag(int_tag_int(Index)) then
|
|
map.det_insert(Index, Label, !IndexMap)
|
|
else
|
|
unexpected($pred, "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) :-
|
|
( if CurVal > LastVal then
|
|
expect(unify(IndexPairs, []), $pred,
|
|
"NextVal > LastVal, IndexList not []"),
|
|
Targets = []
|
|
else
|
|
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,
|
|
( if FirstIndex = CurVal then
|
|
generate_dense_jump_table(NextVal, LastVal, LaterIndexPairs,
|
|
LaterTargets, !MaybeFailLabel, !CI),
|
|
Targets = [yes(FirstLabel) | LaterTargets]
|
|
else
|
|
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)
|
|
).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
:- end_module ll_backend.dense_switch.
|
|
%----------------------------------------------------------------------------%
|