Files
mercury/compiler/dense_switch.m
Peter Wang 0925d052d8 Make type_range also succeed for discontiguous range of enum values.
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.
2021-04-13 15:04:55 +10:00

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.
%----------------------------------------------------------------------------%