Files
mercury/compiler/dense_switch.m
Zoltan Somogyi d8a31e574e Move six utility modules from check_hlds to hlds.
compiler/inst_lookup.m:
compiler/inst_mode_type_prop.m:
compiler/inst_test.m:
compiler/inst_util.m:
compiler/mode_util.m:
compiler/type_util.m:
    Move these modules from the check_hlds package to the hlds package.
    The reason is that all the content of five of these modules, and
    most of the content of one module (inst_util.m) is not used
    exclusively during semantic checking passes. (A later diff
    should deal with the exception.) Some are used by the pass that
    builds the initial HLDS, and all are used by middle-end and backend
    passes. The move therefore reduces the number of inappropriate imports
    of the check_hlds package.

compiler/check_hlds.m:
compiler/hlds.m:
    Effect the transfer.

compiler/*.m:
    Conform to the changes above.
2025-10-08 23:07:13 +11:00

295 lines
11 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1994-2007, 2009-2011 The University of Melbourne.
% Copyright (C) 2015-2021, 2024-2025 The Mercury team.
% 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.lookup_switch_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 hlds.type_util.
:- 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 = do_not_need_range_check,
FirstVal = TypeMin,
LastVal = TypeMax
else
NeedRangeCheck = need_range_check,
FirstVal = LowerLimit,
LastVal = UpperLimit
)
;
CanFail = cannot_fail,
NeedRangeCheck = do_not_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_arith(int_type_int, ao_sub), VarRval,
const(llconst_int(FirstVal)))
),
% Check that the value of the variable lies within the appropriate range
% if necessary.
LastFirstValDifference = LastVal - FirstVal,
(
NeedRangeCheck = need_range_check,
fail_if_rval_is_false(
binop(int_as_uint_cmp(le),
IndexRval, const(llconst_int(LastFirstValDifference))),
RangeCheckCode, !CI, !CLD)
;
NeedRangeCheck = do_not_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, yes(LastFirstValDifference), 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.
%---------------------------------------------------------------------------%