mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 01:13:30 +00:00
library/int32.m:
Add these library operations.
NEWS.md:
Announce the new operations.
compiler/lookup_switch_util.m:
Use the checked operations added by this diff and other recent diffs
to replace unchecked casts.
450 lines
16 KiB
Mathematica
450 lines
16 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 2000-2012 The University of Melbourne.
|
|
% Copyright (C) 2013-2026 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: lookup_switch_util.m.
|
|
% Authors: fjh, zs.
|
|
%
|
|
% This module defines stuff for generating dense and lookup switches
|
|
% that is shared between the MLDS and LLDS back-ends.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module backend_libs.lookup_switch_util.
|
|
:- interface.
|
|
|
|
:- import_module backend_libs.switch_util.
|
|
:- import_module hlds.
|
|
:- import_module hlds.code_model.
|
|
:- import_module hlds.hlds_data.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.prog_type.
|
|
:- import_module parse_tree.set_of_var.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module bool.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Stuff for dense switches.
|
|
%
|
|
|
|
% type_range(ModuleInfo, TypeCtorCategory, Type,
|
|
% NumValuesInRange, Limits):
|
|
%
|
|
% Determine the range [Min..Max] of an atomic type, and the number of
|
|
% values in that range (including both endpoints). Values within the range
|
|
% are not necessarily used by the type.
|
|
% Fail if the type isn't the sort of type that has a range, or
|
|
% if the type's range is too big to do a *dense* switch on (e.g. int).
|
|
%
|
|
:- pred type_range(module_info::in, type_ctor_category::in, mer_type::in,
|
|
uint::out, int_switch_limits::out) is semidet.
|
|
|
|
% switch_density(NumCases, NumValuesInRange):
|
|
%
|
|
% Calculate the percentage density given the range and the number of cases.
|
|
%
|
|
:- func switch_density(uint, uint) = uint.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Stuff for lookup switches.
|
|
%
|
|
|
|
:- type case_consts(Key, Rval, SeveralInfo)
|
|
---> all_one_soln(
|
|
map(Key, list(Rval))
|
|
)
|
|
; some_several_solns(
|
|
map(Key, soln_consts(Rval)),
|
|
SeveralInfo
|
|
).
|
|
|
|
:- type case_consts_several_llds
|
|
---> case_consts_several_llds(
|
|
% The resume vars.
|
|
set_of_progvar,
|
|
|
|
% The Boolean "or" of the result of invoking
|
|
% goal_may_modify_trail on the goal_infos of the switch arms
|
|
% that are disjunctions.
|
|
bool
|
|
).
|
|
|
|
:- type soln_consts(Rval)
|
|
---> one_soln(list(Rval))
|
|
; several_solns(list(Rval), list(list(Rval))).
|
|
% The first solution, and all the later solutions.
|
|
|
|
:- type need_range_check
|
|
---> do_not_need_range_check
|
|
; need_range_check.
|
|
|
|
% do_not_need_bit_vec_check_with_gaps should be used if the
|
|
% generated lookup table is expected to contain dummy rows.
|
|
% Otherwise, do_not_need_bit_vec_check_no_gaps should be used.
|
|
%
|
|
:- type need_bit_vec_check
|
|
---> do_not_need_bit_vec_check_no_gaps
|
|
; do_not_need_bit_vec_check_with_gaps
|
|
; need_bit_vec_check.
|
|
|
|
:- pred filter_out_failing_cases_if_needed(code_model::in,
|
|
list(tagged_case)::in, list(tagged_case)::out,
|
|
can_fail::in, can_fail::out) is det.
|
|
|
|
:- pred find_int_lookup_switch_params(module_info::in, mer_type::in,
|
|
can_fail::in, int_switch_info::in, uint::in,
|
|
need_bit_vec_check::out, need_range_check::out,
|
|
int_switch_limits::out) is semidet.
|
|
|
|
:- pred project_all_to_one_solution(map(Key, soln_consts(Rval))::in,
|
|
map(Key, list(Rval))::out) is semidet.
|
|
|
|
:- pred project_solns_to_rval_lists(assoc_list(T, soln_consts(Rval))::in,
|
|
list(list(Rval))::in, list(list(Rval))::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% These predicates are used in both the LLDS and MLDS backends
|
|
% when testing whether a switch is a lookup switch.
|
|
%
|
|
% Note that for switches on (signed or unsigned, sized or not) integers,
|
|
% we should have checked that both the min and max tag values fit
|
|
% into the range of an int32. Therefore this should also be true for
|
|
% all the case tags we test afterwards.
|
|
%
|
|
|
|
% If the int_tag specifies a value that fits in an int32,
|
|
% return the value; otherwise, abort.
|
|
%
|
|
:- func get_int32_tag_value(int_tag) = int32.
|
|
|
|
% If the cons_tag specifies an int_tag whose value fits in an int32,
|
|
% return the value; otherwise, abort.
|
|
%
|
|
:- func get_int_in_cons_tag(cons_tag) = int.
|
|
|
|
% If the cons_tag specifies a string_tag, return the string;
|
|
% otherwise, abort.
|
|
%
|
|
:- func get_string_in_cons_tag(cons_tag) = string.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module backend_libs.string_encoding.
|
|
:- import_module hlds.type_util.
|
|
:- import_module libs.
|
|
:- import_module libs.globals.
|
|
|
|
:- import_module cord.
|
|
:- import_module int.
|
|
:- import_module int32.
|
|
:- import_module maybe.
|
|
:- import_module one_or_more.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module uint.
|
|
:- import_module uint8.
|
|
:- import_module uint16.
|
|
:- import_module uint32.
|
|
:- import_module uint64.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Stuff for dense switches.
|
|
%
|
|
|
|
type_range(ModuleInfo, TypeCtorCat, Type, NumValuesInRangeU, Limits) :-
|
|
(
|
|
TypeCtorCat = ctor_cat_builtin(cat_builtin_char),
|
|
% Note also that some code in both dense_switch.m and in
|
|
% lookup_switch.m assumes that min_char_value is 0.
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
globals.get_target(Globals, Target),
|
|
target_char_range(Target, Min, Max),
|
|
( if
|
|
int32.from_int(Min, Min32),
|
|
int32.from_int(Max, Max32)
|
|
then
|
|
NumValuesInRangeI32 = Max32 - Min32 + 1i32,
|
|
NumValuesInRangeI = int32.cast_to_int(NumValuesInRangeI32),
|
|
NumValuesInRangeU = uint.cast_from_int(NumValuesInRangeI),
|
|
Limits = int_switch_limits(Min32, Max32)
|
|
else
|
|
unexpected($pred, "char range does not fit in int32")
|
|
)
|
|
;
|
|
TypeCtorCat = ctor_cat_enum(cat_enum_mercury),
|
|
type_to_ctor_det(Type, TypeCtor),
|
|
module_info_get_type_table(ModuleInfo, TypeTable),
|
|
lookup_type_ctor_defn(TypeTable, TypeCtor, TypeDefn),
|
|
hlds_data.get_type_defn_body(TypeDefn, TypeBody),
|
|
(
|
|
TypeBody = hlds_du_type(TypeBodyDu),
|
|
TypeBodyDu = type_body_du(OoMCtors, _, MaybeSuperType, _,
|
|
MaybeRepn, _),
|
|
(
|
|
MaybeRepn = yes(Repn)
|
|
;
|
|
MaybeRepn = no,
|
|
unexpected($pred, "MaybeRepn = no")
|
|
),
|
|
(
|
|
MaybeSuperType = not_a_subtype,
|
|
Min = 0,
|
|
OoMCtors = one_or_more(_HeadCtor, TailCtors),
|
|
list.length(TailCtors, NumTailConstructors),
|
|
% NumConstructors = 1 + NumTailConstructors
|
|
% Max = NumConstructors - 1
|
|
Max = NumTailConstructors
|
|
;
|
|
MaybeSuperType = subtype_of(_),
|
|
% A subtype enum does not necessarily use all values from 0 to
|
|
% the max.
|
|
CtorRepns = Repn ^ dur_ctor_repns,
|
|
ctor_repns_int_tag_range(CtorRepns, Min, Max)
|
|
),
|
|
( if
|
|
int32.from_int(Min, Min32),
|
|
int32.from_int(Max, Max32)
|
|
then
|
|
NumValuesInRangeI32 = Max32 - Min32 + 1i32,
|
|
NumValuesInRangeI = int32.cast_to_int(NumValuesInRangeI32),
|
|
NumValuesInRangeU = uint.cast_from_int(NumValuesInRangeI),
|
|
Limits = int_switch_limits(Min32, Max32)
|
|
else
|
|
fail
|
|
)
|
|
;
|
|
( TypeBody = hlds_eqv_type(_)
|
|
; TypeBody = hlds_foreign_type(_)
|
|
; TypeBody = hlds_solver_type(_)
|
|
; TypeBody = hlds_abstract_type(_)
|
|
),
|
|
unexpected($pred, "enum type is not d.u. type?")
|
|
)
|
|
).
|
|
|
|
:- pred ctor_repns_int_tag_range(list(constructor_repn)::in,
|
|
int::out, int::out) is semidet.
|
|
|
|
ctor_repns_int_tag_range([CtorRepn | CtorRepns], Min, Max) :-
|
|
ConsTag = CtorRepn ^ cr_tag,
|
|
Int = get_enum_int_in_cons_tag(ConsTag),
|
|
list.foldl2(add_to_ctor_repn_int_tag_range, CtorRepns, Int, Min, Int, Max).
|
|
|
|
:- pred add_to_ctor_repn_int_tag_range(constructor_repn::in,
|
|
int::in, int::out, int::in, int::out) is det.
|
|
|
|
add_to_ctor_repn_int_tag_range(CtorRepn, !Min, !Max) :-
|
|
ConsTag = CtorRepn ^ cr_tag,
|
|
Int = get_enum_int_in_cons_tag(ConsTag),
|
|
int.min(Int, !Min),
|
|
int.max(Int, !Max).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
switch_density(NumCases, NumValuesInRange) = Density :-
|
|
Density = (NumCases * 100u) // NumValuesInRange.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Stuff for lookup switches.
|
|
%
|
|
|
|
filter_out_failing_cases_if_needed(CodeModel, !TaggedCases, !SwitchCanFail) :-
|
|
(
|
|
( CodeModel = model_non
|
|
; CodeModel = model_semi
|
|
),
|
|
filter_out_failing_cases(!TaggedCases, !SwitchCanFail)
|
|
;
|
|
CodeModel = model_det
|
|
).
|
|
|
|
:- pred filter_out_failing_cases(list(tagged_case)::in, list(tagged_case)::out,
|
|
can_fail::in, can_fail::out) is det.
|
|
|
|
filter_out_failing_cases(TaggedCases0, TaggedCases, !SwitchCanFail) :-
|
|
filter_out_failing_cases_loop(TaggedCases0, cord.init, TaggedCasesCord,
|
|
!SwitchCanFail),
|
|
TaggedCases = cord.list(TaggedCasesCord).
|
|
|
|
:- pred filter_out_failing_cases_loop(list(tagged_case)::in,
|
|
cord(tagged_case)::in, cord(tagged_case)::out,
|
|
can_fail::in, can_fail::out) is det.
|
|
|
|
filter_out_failing_cases_loop([], !TaggedCasesCord, !SwitchCanFail).
|
|
filter_out_failing_cases_loop([TaggedCase | TaggedCases], !TaggedCasesCord,
|
|
!SwitchCanFail) :-
|
|
TaggedCase = tagged_case(_, _, _, Goal),
|
|
Goal = hlds_goal(GoalExpr, _),
|
|
( if GoalExpr = disj([]) then
|
|
!:SwitchCanFail = can_fail
|
|
else
|
|
cord.snoc(TaggedCase, !TaggedCasesCord)
|
|
),
|
|
filter_out_failing_cases_loop(TaggedCases, !TaggedCasesCord,
|
|
!SwitchCanFail).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
find_int_lookup_switch_params(ModuleInfo, SwitchVarType, SwitchCanFail,
|
|
IntSwitchInfo, ReqDensity, NeedBitVecCheck, NeedRangeCheck, Limits) :-
|
|
IntSwitchInfo = int_switch_info(_IntType,
|
|
NumValuesInRange, NumValuesInCases, CaseLimits),
|
|
|
|
% We want to generate a lookup switch for any switch that is dense enough
|
|
% - we don't care how many cases it has. A memory lookup tends to be
|
|
% cheaper than a branch.
|
|
Density = switch_density(NumValuesInCases, NumValuesInRange),
|
|
Density > ReqDensity,
|
|
|
|
(
|
|
SwitchCanFail = can_fail,
|
|
% For can_fail 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 (with gaps),
|
|
% but then we will need to do the bitvector test.
|
|
classify_type(ModuleInfo, SwitchVarType) = TypeCategory,
|
|
( if
|
|
type_range(ModuleInfo, TypeCategory, SwitchVarType,
|
|
NumValuesInType, TypeLimits),
|
|
DetDensity = switch_density(NumValuesInCases, NumValuesInType),
|
|
DetDensity > ReqDensity
|
|
then
|
|
NeedRangeCheck = do_not_need_range_check,
|
|
NeedBitVecCheck = need_bit_vec_check,
|
|
Limits = TypeLimits
|
|
else
|
|
% First check the variable is in range.
|
|
NeedRangeCheck = need_range_check,
|
|
% We will need to perform the bitvector test if the lookup table
|
|
% is going to contain any gaps.
|
|
( if NumValuesInCases = NumValuesInRange then
|
|
NeedBitVecCheck = do_not_need_bit_vec_check_no_gaps
|
|
else
|
|
NeedBitVecCheck = need_bit_vec_check
|
|
),
|
|
Limits = CaseLimits
|
|
)
|
|
;
|
|
SwitchCanFail = cannot_fail,
|
|
% The cannot_fail guarantees that the values that are in range
|
|
% but are not covered by any of the cases won't actually be reached.
|
|
NeedRangeCheck = do_not_need_range_check,
|
|
% There may be gaps in the lookup table, for example when
|
|
% switching on a variable of a subtype which does not use
|
|
% some values in the range.
|
|
( if NumValuesInCases = NumValuesInRange then
|
|
NeedBitVecCheck = do_not_need_bit_vec_check_no_gaps
|
|
else
|
|
NeedBitVecCheck = do_not_need_bit_vec_check_with_gaps
|
|
),
|
|
Limits = CaseLimits
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
project_all_to_one_solution(CaseSolnsMap, CaseValuesMap) :-
|
|
map.map_values(project_soln_consts_to_one_soln,
|
|
CaseSolnsMap, CaseValuesMap).
|
|
|
|
:- pred project_soln_consts_to_one_soln(Key::in,
|
|
soln_consts(Rval)::in, list(Rval)::out) is semidet.
|
|
|
|
project_soln_consts_to_one_soln(_Key, Solns, Values) :-
|
|
Solns = one_soln(Values).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
project_solns_to_rval_lists([], !RvalsList).
|
|
project_solns_to_rval_lists([Case | Cases], !RvalsList) :-
|
|
Case = _Index - Soln,
|
|
(
|
|
Soln = one_soln(Rvals),
|
|
!:RvalsList = [Rvals | !.RvalsList]
|
|
;
|
|
Soln = several_solns(FirstSolnRvals, LaterSolnsRvalsList),
|
|
!:RvalsList = [FirstSolnRvals | LaterSolnsRvalsList] ++ !.RvalsList
|
|
),
|
|
project_solns_to_rval_lists(Cases, !RvalsList).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
get_int32_tag_value(IntTag) = I32 :-
|
|
(
|
|
IntTag = int_tag_int(IW),
|
|
I32 = int32.det_from_int(IW)
|
|
;
|
|
IntTag = int_tag_int8(I8),
|
|
I32 = int32.cast_from_int8(I8)
|
|
;
|
|
IntTag = int_tag_int16(I16),
|
|
I32 = int32.cast_from_int16(I16)
|
|
;
|
|
IntTag = int_tag_int32(I32)
|
|
;
|
|
IntTag = int_tag_int64(I64),
|
|
I32 = int32.det_from_int64(I64)
|
|
;
|
|
IntTag = int_tag_uint(U),
|
|
I32 = int32.cast_from_int(uint.cast_to_int(U))
|
|
;
|
|
IntTag = int_tag_uint8(U8),
|
|
I32 = int32.cast_from_int(uint8.cast_to_int(U8))
|
|
;
|
|
IntTag = int_tag_uint16(U16),
|
|
I32 = int32.cast_from_int(uint16.cast_to_int(U16))
|
|
;
|
|
IntTag = int_tag_uint32(U32),
|
|
I32 = int32.det_from_int(uint32.det_to_int(U32))
|
|
;
|
|
IntTag = int_tag_uint64(U64),
|
|
I32 = int32.det_from_int(uint64.det_to_int(U64))
|
|
).
|
|
|
|
:- func get_enum_int_in_cons_tag(cons_tag) = int.
|
|
|
|
get_enum_int_in_cons_tag(ConsTag) = I :-
|
|
( if ConsTag = int_tag(int_tag_int(IPrime)) then
|
|
I = IPrime
|
|
else
|
|
unexpected($pred, "not int_tag_int")
|
|
).
|
|
|
|
get_int_in_cons_tag(ConsTag) = I :-
|
|
( if ConsTag = int_tag(IntTag) then
|
|
I32 = get_int32_tag_value(IntTag),
|
|
I = int32.cast_to_int(I32)
|
|
else
|
|
unexpected($pred, "not int_tag")
|
|
).
|
|
|
|
get_string_in_cons_tag(ConsTag) = Str :-
|
|
( if ConsTag = string_tag(StrPrime) then
|
|
Str = StrPrime
|
|
else
|
|
unexpected($pred, "not string_tag")
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module backend_libs.lookup_switch_util.
|
|
%---------------------------------------------------------------------------%
|