Files
mercury/compiler/lookup_switch.m
Julien Fischer ca9fb057a1 Simplify some code.
compiler/handle_options.m:
compiler/intermod_analysis.m:
compiler/lookup_switch.m:
compiler/ml_top_gen.m:
compiler/modecheck_goal.m:
compiler/options_file.m:
compiler/string_switch.m:
compiler/term_constr_build.m:
compiler/term_constr_data.m:
compiler/term_constr_initial.m:
compiler/term_constr_util.m:
grade_lib/grade_setup.m:
     Replace the use of some predicates from the std_util module where more
     direct alternatives now exist.

     Update copyright notices.
2022-12-14 01:13:58 +11:00

975 lines
43 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1996-2012 The University of Melbourne.
% Copyright (C) 2015, 2017-2018, 2020-2022 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.m.
% Authors: conway, zs.
%
% For switches on atomic types in which the cases contain only the
% construction of constants, generate code which just assigns the values of
% the output variables by indexing into an array of values for the output
% variables.
%
% For switches that can fail, the generated code does a range check on the
% index, and then does a lookup in a bit-vector to see if there is a value for
% the appropriate case. If there is, then it does a lookup (using the MR_field
% macro) in the array of results. The array is padded with "0"s for cases that
% are not covered. This is fine, since we do the lookup after we check the
% bit-vector for the appropriate case.
%
% The current implementation works out whether or not it can do a lookup
% switch by generating code for each case and looking to see that no code got
% generated (i.e. only the code generation state got modified) and that the
% output variables of the switch are all constants. This is potentially quite
% inefficient because it does the work of generating code for the cases and
% then may throw it away if a subsequent case generates actual code, or non
% constant outputs.
%
% The number of bits per word is taken from the bits_per_word option which
% uses a flag in the mmc script with a value from configuration. This is used
% when generating bit-vectors.
%
% The implementation of lookup switches is further documented in the paper
% Dealing with large predicates: exo-compilation in the WAM and in Mercury,
% by Bart Demoen, Phuong-Lan Nguyen, Vi'tor Santos Costa and Zoltan Somogyi.
%
% The module ml_lookup_switch.m implements lookup switches for the MLDS
% backend. Any changes here may need to be reflected there as well.
%
%-----------------------------------------------------------------------------%
:- module ll_backend.lookup_switch.
:- interface.
:- import_module backend_libs.
:- import_module backend_libs.switch_util.
:- import_module hlds.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_llds.
:- 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 parse_tree.set_of_var.
:- import_module list.
%-----------------------------------------------------------------------------%
:- type lookup_switch_info(Key)
---> lookup_switch_info(
% The map from the switched-on value to the values of the
% variables in each solution.
%
% XXX In the MLDS backend, the equivalent type maps case_ids
% to the values of the variables in each solution. This is
% to support tries, which the LLDS backend does not support
% (yet).
lsi_cases :: case_consts(Key, rval,
case_consts_several_llds),
% The output variables, which become (some of) the fields
% in each row of a lookup table.
lsi_out_variables :: list(prog_var),
% The types of the fields holding output variables.
lsi_out_types :: list(llds_type),
lsi_liveness :: set_of_progvar
).
% Decide whether we can generate code for this switch using a lookup table.
%
:- pred is_lookup_switch(position_info::in,
pred(cons_tag, Key)::in(pred(in, out) is det),
list(tagged_case)::in, hlds_goal_info::in, abs_store_map::in,
branch_end::in, branch_end::out, lookup_switch_info(Key)::out,
code_info::in, code_info::out) is semidet.
% Generate code for the switch that the lookup_switch_info came from.
%
:- pred generate_int_lookup_switch(rval::in, lookup_switch_info(int)::in,
label::in, abs_store_map::in, int::in, int::in,
need_bit_vec_check::in, need_range_check::in,
branch_end::in, branch_end::out, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
:- type case_kind
---> kind_zero_solns
; kind_one_soln
; kind_several_solns.
% generate_code_for_all_kinds(Kinds, NumPrevColumns, OutVars, ResumeVars,
% EndLabel, StoreMap, Liveness, AddTrailOps,
% BaseReg, LaterVectorAddrRval, Code, !CI, !.CLD):
%
% Generate code for the kinds of solution cardinalities listed in Kinds.
%
% - For kind_zero_solns, generate code that performs failure.
% - For kind_one_solution, generate code that looks up the main table
% at row BaseReg and then goes to EndLabel.
% - For kind_several_solns, generate code that looks up the main table
% at row BaseReg, sets up a resume point that stores ResumeVars,
% succeeds by going to EndLabel. On backtracking, the generated code
% will keep returning rows from the later solution table until there are
% no more later solutions associated with row BaseReg.
%
% The definition of EndLabel is up to the caller.
%
% For this predicate, the main table's columns form three groups.
%
% - The first group of NumPrevColumns columns are ignored by this
% predicate.
% - For int switches, there will be no previous columns.
% - For binary string switches, there is one containing the string.
% - For hash string switches, there are one or two, the first containing
% the string, and the second (if it is needed) the number of the next
% slot in the open addressing sequence.
%
% - The second group contains two columns, which contain respectively
% the offsets of the first and last later solutions in the later
% solutions table. Each of these offsets is the offset of the first
% column of the selected row. An offset of zero indicates there is
% no later solution, which means that the first row of the later solution
% table can never be referred to, and must therefore be a dummy.
%
% - The third group consists of the values of OutVars.
%
% Therefore each row of the main table has NumPrevColumns + 2 + |OutVars|
% columns, while the later solutions table has |OutVars| columns.
%
% This predicate assumes that the caller has already set BaseReg to point
% to the start of the relevant row in the main solutions table.
% LaterVectorAddrRval should be the address of the start of the later
% solutions table.
%
:- pred generate_code_for_all_kinds(list(case_kind)::in, int::in,
list(prog_var)::in, set_of_progvar::in, label::in, abs_store_map::in,
set_of_progvar::in, add_trail_ops::in, lval::in, rval::in,
branch_end::in, branch_end::out, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
:- func default_value_for_type(llds_type) = rval.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module backend_libs.builtin_ops.
:- import_module hlds.code_model.
:- import_module hlds.goal_form.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module ll_backend.continuation_info.
:- import_module ll_backend.global_data.
:- import_module ll_backend.lookup_util.
:- import_module parse_tree.var_table.
:- import_module assoc_list.
:- import_module bool.
:- import_module cord.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module string.
%-----------------------------------------------------------------------------%
is_lookup_switch(BranchStart, GetTag, TaggedCases, GoalInfo, StoreMap,
!MaybeEnd, LookupSwitchInfo, !CI) :-
% Most of this predicate is taken from dense_switch.m.
% We need the code_info structure to generate code for the cases to
% get the constants (if they exist). We can't throw it away at the
% end because we may have allocated some new static ground terms.
reset_to_position(BranchStart, !.CI, StartCLD),
figure_out_output_vars(!.CI, StartCLD, GoalInfo, OutVars),
set_of_var.list_to_set(OutVars, ArmNonLocals),
generate_constants_for_lookup_switch(BranchStart, GetTag, TaggedCases,
OutVars, ArmNonLocals, StoreMap, Liveness, map.init, CaseSolnMap,
!MaybeEnd, set_of_var.init, ResumeVars, no, GoalsMayModifyTrail, !CI),
get_var_table(!.CI, VarTable),
OutTypes = list.map(lookup_var_type_func(VarTable), OutVars),
( if project_all_to_one_solution(CaseSolnMap, CaseValuePairsMap) then
CaseConsts = all_one_soln(CaseValuePairsMap)
else
CaseConsts = some_several_solns(CaseSolnMap,
case_consts_several_llds(ResumeVars, GoalsMayModifyTrail))
),
get_exprn_opts(!.CI, ExprnOpts),
UnboxFloats = get_unboxed_floats(ExprnOpts),
UnboxInt64s = get_unboxed_int64s(ExprnOpts),
map.to_assoc_list(CaseSolnMap, CaseSolns),
% This generates CaseValues in reverse order of index, but given that
% we only use CaseValues to find out the right OutLLDSTypes, this is OK.
project_solns_to_rval_lists(CaseSolns, [], CaseValues),
find_general_llds_types(UnboxFloats, UnboxInt64s, OutTypes, CaseValues,
OutLLDSTypes),
LookupSwitchInfo = lookup_switch_info(CaseConsts, OutVars, OutLLDSTypes,
Liveness).
%---------------------------------------------------------------------------%
:- pred generate_constants_for_lookup_switch(position_info::in,
pred(cons_tag, Key)::in(pred(in, out) is det),
list(tagged_case)::in, list(prog_var)::in, set_of_progvar::in,
abs_store_map::in, set_of_progvar::out,
map(Key, soln_consts(rval))::in, map(Key, soln_consts(rval))::out,
branch_end::in, branch_end::out, set_of_progvar::in, set_of_progvar::out,
bool::in, bool::out, code_info::in, code_info::out) is semidet.
generate_constants_for_lookup_switch(_BranchStart, _GetTag,
[], _Vars, _ArmNonLocals, _StoreMap, set_of_var.init,
!IndexMap, !MaybeEnd, !ResumeVars, !GoalsMayModifyTrail, !CI).
generate_constants_for_lookup_switch(BranchStart, GetTag,
[TaggedCase | TaggedCases], Vars, ArmNonLocals, StoreMap, Liveness,
!IndexMap, !MaybeEnd, !ResumeVars, !GoalsMayModifyTrail, !CI) :-
TaggedCase = tagged_case(TaggedMainConsId, TaggedOtherConsIds, _, Goal),
Goal = hlds_goal(GoalExpr, GoalInfo),
% Goals with these features need special treatment in generate_goal.
Features = goal_info_get_features(GoalInfo),
not set.member(feature_call_table_gen, Features),
not set.member(feature_save_deep_excp_vars, Features),
( if GoalExpr = disj(Disjuncts) then
(
Disjuncts = [],
% Cases like this should have been filtered out by
% filter_out_failing_cases.
unexpected($pred, "disj([])")
;
Disjuncts = [FirstDisjunct | LaterDisjuncts],
goal_is_conj_of_unify(ArmNonLocals, FirstDisjunct),
all_disjuncts_are_conj_of_unify(ArmNonLocals, LaterDisjuncts),
bool.or(goal_may_modify_trail(GoalInfo), !GoalsMayModifyTrail),
FirstDisjunct = hlds_goal(_, FirstDisjunctGoalInfo),
goal_info_get_resume_point(FirstDisjunctGoalInfo, ThisResumePoint),
(
ThisResumePoint = resume_point(ThisResumeVars, _),
set_of_var.union(ThisResumeVars, !ResumeVars)
;
ThisResumePoint = no_resume_point
),
% The pre- and post-goal updates for the disjuncts themselves are
% done as part of the call to generate_goal in
% generate_constants_for_disjuncts in lookup_util.m.
some [!CLD] (
reset_to_position(BranchStart, !.CI, !:CLD),
pre_goal_update(GoalInfo, has_subgoals, !CLD),
remember_position(!.CLD, GoalBranchStart)
),
generate_constants_for_disjunct(GoalBranchStart, FirstDisjunct,
Vars, StoreMap, FirstSoln, !MaybeEnd, Liveness, !CI),
generate_constants_for_disjuncts(GoalBranchStart, LaterDisjuncts,
Vars, StoreMap, LaterSolns, !MaybeEnd, !CI),
% Don't apply the post goal update, since nothing would see
% the result.
SolnConsts = several_solns(FirstSoln, LaterSolns)
)
else
goal_is_conj_of_unify(ArmNonLocals, Goal),
% The pre- and post-goal updates for the goals themselves
% are done as part of the call to generate_goal in
% generate_constants_for_disjuncts in lookup_util.m.
generate_constants_for_arm(BranchStart, Goal, Vars, StoreMap, Soln,
!MaybeEnd, Liveness, !CI),
SolnConsts = one_soln(Soln)
),
record_lookup_for_tagged_cons_id(GetTag, SolnConsts,
TaggedMainConsId, !IndexMap),
record_lookup_for_tagged_cons_ids(GetTag, SolnConsts,
TaggedOtherConsIds, !IndexMap),
generate_constants_for_lookup_switch(BranchStart, GetTag,
TaggedCases, Vars, ArmNonLocals, StoreMap, _LivenessRest,
!IndexMap, !MaybeEnd, !ResumeVars, !GoalsMayModifyTrail, !CI).
:- pred record_lookup_for_tagged_cons_ids(
pred(cons_tag, Key)::in(pred(in, out) is det),
soln_consts(rval)::in, list(tagged_cons_id)::in,
map(Key, soln_consts(rval))::in, map(Key, soln_consts(rval))::out) is det.
record_lookup_for_tagged_cons_ids(_GetTag, _SolnConsts, [], !IndexMap).
record_lookup_for_tagged_cons_ids(GetTag, SolnConsts,
[TaggedConsId | TaggedConsIds], !IndexMap) :-
record_lookup_for_tagged_cons_id(GetTag, SolnConsts,
TaggedConsId, !IndexMap),
record_lookup_for_tagged_cons_ids(GetTag, SolnConsts,
TaggedConsIds, !IndexMap).
:- pred record_lookup_for_tagged_cons_id(
pred(cons_tag, Key)::in(pred(in, out) is det),
soln_consts(rval)::in, tagged_cons_id::in,
map(Key, soln_consts(rval))::in, map(Key, soln_consts(rval))::out) is det.
record_lookup_for_tagged_cons_id(GetTag, SolnConsts, TaggedConsId,
!IndexMap) :-
TaggedConsId = tagged_cons_id(_ConsId, ConsTag),
GetTag(ConsTag, Index),
map.det_insert(Index, SolnConsts, !IndexMap).
%---------------------------------------------------------------------------%
generate_int_lookup_switch(VarRval, LookupSwitchInfo, EndLabel, StoreMap,
StartVal, EndVal, NeedBitVecCheck, NeedRangeCheck, !MaybeEnd, Code,
!CI, !.CLD) :-
LookupSwitchInfo = lookup_switch_info(CaseConsts, OutVars, OutTypes,
Liveness),
% If the case values start at some number other than 0,
% then subtract that number to give us a zero-based index.
( if StartVal = 0 then
IndexRval = VarRval
else
IndexRval = binop(int_sub(int_type_int), VarRval,
const(llconst_int(StartVal)))
),
% If the switch is not locally deterministic, we may need to check that
% the value of the variable lies within the appropriate range.
(
NeedRangeCheck = need_range_check,
Difference = EndVal - StartVal,
CmpRval = binop(unsigned_le, IndexRval,
const(llconst_int(Difference))),
fail_if_rval_is_false(CmpRval, RangeCheckCode, !CI, !CLD)
;
NeedRangeCheck = dont_need_range_check,
RangeCheckCode = empty
),
(
CaseConsts = all_one_soln(CaseValuesMap),
Comment = cord.singleton(
llds_instr(comment("simple lookup switch"), "")
),
map.to_assoc_list(CaseValuesMap, CaseValues),
generate_simple_int_lookup_switch(IndexRval, StoreMap,
StartVal, EndVal, CaseValues, OutVars, OutTypes,
NeedBitVecCheck, Liveness, RestCode, !CI, !.CLD)
;
CaseConsts = some_several_solns(CaseSolnMap,
case_consts_several_llds(ResumeVars, GoalsMayModifyTrail)),
(
GoalsMayModifyTrail = yes,
get_emit_trail_ops(!.CI, EmitTrailOps),
AddTrailOps = EmitTrailOps
;
GoalsMayModifyTrail = no,
AddTrailOps = do_not_add_trail_ops
),
Comment = cord.singleton(
llds_instr(comment("several soln lookup switch"), "")
),
map.to_assoc_list(CaseSolnMap, CaseSolns),
generate_several_soln_int_lookup_switch(IndexRval, EndLabel, StoreMap,
StartVal, EndVal, CaseSolns, ResumeVars, AddTrailOps, OutVars,
OutTypes, NeedBitVecCheck, Liveness, !MaybeEnd, RestCode,
!CI, !.CLD)
),
Code = Comment ++ RangeCheckCode ++ RestCode.
:- pred generate_simple_int_lookup_switch(rval::in, abs_store_map::in,
int::in, int::in, assoc_list(int, list(rval))::in,
list(prog_var)::in, list(llds_type)::in, need_bit_vec_check::in,
set_of_progvar::in, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_simple_int_lookup_switch(IndexRval, StoreMap, StartVal, EndVal,
CaseValues, OutVars, OutTypes, NeedBitVecCheck, Liveness, Code,
!CI, !.CLD) :-
(
NeedBitVecCheck = need_bit_vec_check,
generate_bitvec_test(IndexRval, CaseValues, StartVal, EndVal,
CheckBitVecCode, !CI, !CLD)
;
( NeedBitVecCheck = dont_need_bit_vec_check_no_gaps
; NeedBitVecCheck = dont_need_bit_vec_check_with_gaps
),
CheckBitVecCode = empty
),
% Now generate the static cells into which we do the lookups of the values
% of the output variables, if there are any.
%
% Note that invoking generate_simple_terms when OutVars = [] would lead to
% a compiler abort, since we cannot create C structures with zero fields.
% This can happen for semidet switches.
(
OutVars = [],
BaseRegInitCode = empty
;
OutVars = [_ | _],
% Since we release BaseReg only after the call to generate_branch_end,
% we must make sure that generate_branch_end won't want to overwrite
% BaseReg.
acquire_reg_not_in_storemap(StoreMap, reg_r, BaseReg, !CLD),
% Generate the static lookup table for this switch.
list.length(OutVars, NumOutVars),
construct_simple_int_lookup_vector(CaseValues, StartVal, OutTypes,
[], RevVectorRvals),
list.reverse(RevVectorRvals, VectorRvals),
add_vector_static_cell(OutTypes, VectorRvals, VectorAddr, !CI),
VectorAddrRval = const(llconst_data_addr(VectorAddr, no)),
% Generate code to look up each of the variables in OutVars
% in its slot in the table row IndexRval (which will be row
% VarRval - StartVal). Most of the change is done by
% generate_offset_assigns associating each var with the relevant field
% in !CI.
( if NumOutVars = 1 then
BaseRval = IndexRval
else
BaseRval = binop(int_mul(int_type_int),
IndexRval, const(llconst_int(NumOutVars)))
),
BaseRegInitCode = cord.singleton(
llds_instr(
assign(BaseReg,
mem_addr(heap_ref(VectorAddrRval, yes(ptag(0u8)),
BaseRval))),
"Compute base address for this case")
),
generate_offset_assigns(OutVars, 0, BaseReg, !.CI, !CLD)
),
% We keep track of what variables are supposed to be live at the end
% of cases. We have to do this explicitly because generating a `fail' slot
% last would yield the wrong liveness.
set_liveness_and_end_branch(StoreMap, Liveness, no, _MaybeEnd,
BranchEndCode, !.CLD),
Code = CheckBitVecCode ++ BaseRegInitCode ++ BranchEndCode.
%-----------------------------------------------------------------------------%
:- pred construct_simple_int_lookup_vector(assoc_list(int, list(rval))::in,
int::in, list(llds_type)::in,
list(list(rval))::in, list(list(rval))::out) is det.
construct_simple_int_lookup_vector([], _, _, !RevRows).
construct_simple_int_lookup_vector([Index - Rvals | Rest], CurIndex, OutTypes,
!RevRows) :-
( if CurIndex < Index then
% If this argument (array element) is a place-holder and
% will never be referenced, just fill it in with a dummy entry.
Row = list.map(default_value_for_type, OutTypes),
Remainder = [Index - Rvals | Rest]
else
Row = Rvals,
Remainder = Rest
),
!:RevRows = [Row | !.RevRows],
construct_simple_int_lookup_vector( Remainder, CurIndex + 1, OutTypes,
!RevRows).
%-----------------------------------------------------------------------------%
:- pred generate_several_soln_int_lookup_switch(rval::in, label::in,
abs_store_map::in, int::in, int::in,
assoc_list(int, soln_consts(rval))::in, set_of_progvar::in,
add_trail_ops::in, list(prog_var)::in, list(llds_type)::in,
need_bit_vec_check::in, set_of_progvar::in,
branch_end::in, branch_end::out, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in) is det.
generate_several_soln_int_lookup_switch(IndexRval, EndLabel, StoreMap,
StartVal, EndVal, CaseSolns, ResumeVars, AddTrailOps,
OutVars, OutTypes, NeedBitVecCheck, Liveness, !MaybeEnd, Code,
!CI, !.CLD) :-
% If there are no output variables, then how can the individual solutions
% differ from each other?
expect_not(unify(OutVars, []), $pred, "no OutVars"),
% Now generate the static cells into which we do the lookups of the values
% of the output variables, if there are any.
%
% We put a dummy row at the start of the later solns table, so that
% a zero in the "later solns start row" column of the main table can mean
% "no later solutions".
list.length(OutTypes, NumOutTypes),
InitLaterSolnRowNumber = 1,
DummyLaterSolnRow = list.map(default_value_for_type, OutTypes),
LaterSolnArrayCord0 = cord.singleton(DummyLaterSolnRow),
construct_several_soln_int_lookup_vector(StartVal, EndVal,
OutTypes, NumOutTypes, CaseSolns, MainRows,
InitLaterSolnRowNumber, LaterSolnArrayCord0, LaterSolnArrayCord,
0, FailCaseCount, 0, OneSolnCaseCount, 0, SeveralSolnCaseCount),
LaterSolnArray = cord.list(LaterSolnArrayCord),
( if
(
NeedBitVecCheck = need_bit_vec_check
<=>
FailCaseCount > 0
)
then
true
else
unexpected($pred, "bad FailCaseCount")
),
MainRowTypes = [lt_int(int_type_int), lt_int(int_type_int) | OutTypes],
list.length(MainRowTypes, MainNumColumns),
add_vector_static_cell(MainRowTypes, MainRows, MainVectorAddr, !CI),
MainVectorAddrRval = const(llconst_data_addr(MainVectorAddr, no)),
add_vector_static_cell(OutTypes, LaterSolnArray, LaterVectorAddr, !CI),
LaterVectorAddrRval = const(llconst_data_addr(LaterVectorAddr, no)),
list.sort([FailCaseCount - kind_zero_solns,
OneSolnCaseCount - kind_one_soln,
SeveralSolnCaseCount - kind_several_solns], AscendingSortedCountKinds),
list.reverse(AscendingSortedCountKinds, DescendingSortedCountKinds),
assoc_list.values(DescendingSortedCountKinds, DescendingSortedKinds),
% Since we release BaseReg only after the calls to generate_branch_end,
% we must make sure that generate_branch_end won't want to overwrite
% BaseReg.
acquire_reg_not_in_storemap(StoreMap, reg_r, BaseReg, !CLD),
% IndexRval has already had Start subtracted from it.
BaseRegInitCode = cord.singleton(
llds_instr(
assign(BaseReg,
mem_addr(heap_ref(MainVectorAddrRval, yes(ptag(0u8)),
binop(int_mul(int_type_int),
IndexRval,
const(llconst_int(MainNumColumns)))))),
"Compute base address for this case")
),
generate_code_for_all_kinds(DescendingSortedKinds, 0, OutVars, ResumeVars,
EndLabel, StoreMap, Liveness, AddTrailOps,
BaseReg, LaterVectorAddrRval, !MaybeEnd, KindsCode, !CI, !.CLD),
EndLabelCode = cord.singleton(
llds_instr(label(EndLabel),
"end of int several soln lookup switch")
),
Code = BaseRegInitCode ++ KindsCode ++ EndLabelCode.
generate_code_for_all_kinds(Kinds, NumPrevColumns, OutVars, ResumeVars,
EndLabel, StoreMap, Liveness, AddTrailOps,
BaseReg, LaterVectorAddrRval, !MaybeEnd, Code, !CI, !.CLD) :-
% We release BaseReg in each arm of generate_code_for_each_kind below.
% We cannot release it at the bottom of this predicate, because in the
% kind_several_solns arm of generate_code_for_each_kind the generation
% of the resume point will clobber the set of acquired registers.
%
% We cannot release the stack slots anywhere, since they will be needed
% after backtracking to later alternatives of any model_non switch arm.
acquire_temp_slot(slot_lookup_switch_cur, non_persistent_temp_slot,
CurSlot, !CI, !CLD),
acquire_temp_slot(slot_lookup_switch_max, non_persistent_temp_slot,
MaxSlot, !CI, !CLD),
remember_position(!.CLD, BranchStart),
generate_code_for_each_kind(Kinds, NumPrevColumns,OutVars, ResumeVars,
BranchStart, EndLabel, StoreMap, Liveness, AddTrailOps, BaseReg,
CurSlot, MaxSlot, LaterVectorAddrRval, !MaybeEnd, Code, !CI).
:- func case_kind_to_string(case_kind) = string.
case_kind_to_string(kind_zero_solns) = "kind_zero_solns".
case_kind_to_string(kind_one_soln) = "kind_one_soln".
case_kind_to_string(kind_several_solns) = "kind_several_solns".
:- pred generate_code_for_each_kind(list(case_kind)::in, int::in,
list(prog_var)::in, set_of_progvar::in, position_info::in,
label::in, abs_store_map::in, set_of_progvar::in, add_trail_ops::in,
lval::in, lval::in, lval::in, rval::in,
branch_end::in, branch_end::out, llds_code::out,
code_info::in, code_info::out) is det.
generate_code_for_each_kind([], _, _, _, _, _, _, _, _, _, _, _, _,
!MaybeEnd, _, !CI) :-
unexpected($pred, "no kinds").
generate_code_for_each_kind([Kind | Kinds], NumPrevColumns,
OutVars, ResumeVars, BranchStart, EndLabel, StoreMap, Liveness,
AddTrailOps, BaseReg, CurSlot, MaxSlot, LaterVectorAddrRval,
!MaybeEnd, Code, !CI) :-
(
Kind = kind_zero_solns,
TestOp = int_ge(int_type_int),
some [!CLD] (
reset_to_position(BranchStart, !.CI, !:CLD),
release_reg(BaseReg, !CLD),
generate_failure(KindCode, !CI, !.CLD)
)
;
Kind = kind_one_soln,
TestOp = ne(int_type_int),
some [!CLD] (
reset_to_position(BranchStart, !.CI, !:CLD),
generate_offset_assigns(OutVars, NumPrevColumns + 2, BaseReg,
!.CI, !CLD),
set_liveness_and_end_branch(StoreMap, Liveness, !MaybeEnd,
BranchEndCode, !.CLD)
),
GotoEndCode = cord.singleton(
llds_instr(goto(code_label(EndLabel)),
"goto end of switch from one_soln")
),
KindCode = BranchEndCode ++ GotoEndCode
;
Kind = kind_several_solns,
TestOp = int_le(int_type_int),
get_globals(!.CI, Globals),
some [!CLD] (
reset_to_position(BranchStart, !.CI, !:CLD),
% The code below is modelled on the code in disj_gen, but is
% specialized for the situation here.
produce_vars(set_of_var.to_sorted_list(ResumeVars), ResumeMap,
FlushCode, !CLD),
MinOffsetColumnRval = const(llconst_int(NumPrevColumns)),
MaxOffsetColumnRval = const(llconst_int(NumPrevColumns + 1)),
SaveSlotsCode = cord.from_list([
llds_instr(assign(CurSlot,
lval(field(yes(ptag(0u8)), lval(BaseReg),
MinOffsetColumnRval))),
"Setup current slot in the later solution array"),
llds_instr(assign(MaxSlot,
lval(field(yes(ptag(0u8)), lval(BaseReg),
MaxOffsetColumnRval))),
"Setup maximum slot in the later solution array")
]),
maybe_save_ticket(AddTrailOps, SaveTicketCode, MaybeTicketSlot,
!CI, !CLD),
globals.lookup_bool_option(Globals, reclaim_heap_on_nondet_failure,
ReclaimHeap),
maybe_save_hp(ReclaimHeap, SaveHpCode, MaybeHpSlot, !CI, !CLD),
prepare_for_disj_hijack(model_non, HijackInfo, PrepareHijackCode,
!CI, !CLD),
remember_position(!.CLD, DisjEntry),
% Generate code for the non-last disjunct.
make_resume_point(ResumeVars, resume_locs_stack_only, ResumeMap,
ResumePoint, !CI),
effect_resume_point(ResumePoint, model_non, UpdateRedoipCode,
!CLD),
generate_offset_assigns(OutVars, NumPrevColumns + 2, BaseReg,
!.CI, !CLD),
flush_resume_vars_to_stack(FirstFlushResumeVarsCode, !CLD),
% Forget the variables that are needed only at the resumption point
% at the start of the next disjunct, so that we don't generate
% exceptions when their storage is clobbered by the movement
% of the live variables to the places indicated in the store map.
pop_resume_point(!CLD),
pickup_zombies(FirstZombies, !CLD),
make_vars_forward_dead(FirstZombies, !CLD),
set_liveness_and_end_branch(StoreMap, Liveness, !MaybeEnd,
FirstBranchEndCode, !.CLD)
),
GotoEndCode = cord.singleton(
llds_instr(goto(code_label(EndLabel)),
"goto end of switch from several_soln")
),
some [!CLD] (
reset_to_position(DisjEntry, !.CI, !:CLD),
generate_resume_point(ResumePoint, ResumePointCode, !CI, !CLD),
maybe_reset_ticket(MaybeTicketSlot, reset_reason_undo,
RestoreTicketCode),
maybe_restore_hp(MaybeHpSlot, RestoreHpCode),
acquire_reg_not_in_storemap(StoreMap, reg_r, LaterBaseReg, !CLD),
get_next_label(UndoLabel, !CI),
get_next_label(AfterUndoLabel, !CI),
list.length(OutVars, NumOutVars),
TestMoreSolnsCode = cord.from_list([
llds_instr(assign(LaterBaseReg, lval(CurSlot)),
"Init later base register"),
llds_instr(
if_val(binop(int_ge(int_type_int),
lval(LaterBaseReg), lval(MaxSlot)),
code_label(UndoLabel)),
"Jump to undo hijack code if there are no more solutions"),
llds_instr(assign(CurSlot,
binop(int_add(int_type_int),
lval(CurSlot),
const(llconst_int(NumOutVars)))),
"Update current slot in the later solution array"),
llds_instr(goto(code_label(AfterUndoLabel)),
"Jump around undo hijack code"),
llds_instr(label(UndoLabel),
"Undo hijack code")
]),
undo_disj_hijack(HijackInfo, UndoHijackCode, !CLD),
AfterUndoLabelCode = cord.from_list([
llds_instr(label(AfterUndoLabel),
"Return later answer code"),
llds_instr(assign(LaterBaseReg,
mem_addr(heap_ref(LaterVectorAddrRval, yes(ptag(0u8)),
lval(LaterBaseReg)))),
"Compute base address in later array for this solution")
]),
% We need to call effect_resume_point in order to push ResumePoint
% onto the failure continuation stack, so pop_resume_point can pop
% it off. However, since the redoip already points there, we don't
% need to execute _LaterUpdateRedoipCode.
effect_resume_point(ResumePoint, model_non,
_LaterUpdateRedoipCode, !CLD),
generate_offset_assigns(OutVars, 0, LaterBaseReg, !.CI, !CLD),
flush_resume_vars_to_stack(LaterFlushResumeVarsCode, !CLD),
% Forget the variables that are needed only at the resumption point
% at the start of the next disjunct, so that we don't generate
% exceptions when their storage is clobbered by the movement
% of the live variables to the places indicated in the store map.
pop_resume_point(!CLD),
pickup_zombies(LaterZombies, !CLD),
make_vars_forward_dead(LaterZombies, !CLD),
set_liveness_and_end_branch(StoreMap, Liveness, !MaybeEnd,
LaterBranchEndCode, !.CLD)
),
KindCode = FlushCode ++ SaveSlotsCode ++
SaveTicketCode ++ SaveHpCode ++ PrepareHijackCode ++
UpdateRedoipCode ++ FirstFlushResumeVarsCode ++
FirstBranchEndCode ++ GotoEndCode ++ ResumePointCode ++
RestoreTicketCode ++ RestoreHpCode ++
TestMoreSolnsCode ++ UndoHijackCode ++ AfterUndoLabelCode ++
LaterFlushResumeVarsCode ++ LaterBranchEndCode ++ GotoEndCode
),
(
Kinds = [],
Code = KindCode
;
Kinds = [NextKind | _],
get_next_label(NextKindLabel, !CI),
TestRval = binop(TestOp,
lval(field(yes(ptag(0u8)), lval(BaseReg),
const(llconst_int(NumPrevColumns)))),
const(llconst_int(0))),
TestCode = cord.from_list([
llds_instr(if_val(TestRval, code_label(NextKindLabel)),
"skip to next kind in several_soln lookup switch"),
llds_instr(comment("This kind is " ++ case_kind_to_string(Kind)),
"")
]),
NextKindLabelCode = cord.from_list([
llds_instr(label(NextKindLabel),
"next kind in several_soln lookup switch"),
llds_instr(comment("Next kind is "
++ case_kind_to_string(NextKind)),
"")
]),
generate_code_for_each_kind(Kinds, NumPrevColumns, OutVars, ResumeVars,
BranchStart, EndLabel, StoreMap, Liveness, AddTrailOps,
BaseReg, CurSlot, MaxSlot, LaterVectorAddrRval,
!MaybeEnd, LaterKindsCode, !CI),
Code = TestCode ++ KindCode ++ NextKindLabelCode ++ LaterKindsCode
).
% Note that we specify --optimise-constructor-last-call for this module
% in order to make this predicate tail recursive.
%
:- pred construct_several_soln_int_lookup_vector(int::in, int::in,
list(llds_type)::in, int::in, assoc_list(int, soln_consts(rval))::in,
list(list(rval))::out,
int::in, cord(list(rval))::in, cord(list(rval))::out,
int::in, int::out, int::in, int::out, int::in, int::out) is det.
construct_several_soln_int_lookup_vector(CurIndex, EndVal,
OutTypes, NumOutTypes, [], MainRows,
!.LaterNextRow, !LaterSolnArray,
!FailCaseCount, !OneSolnCaseCount, !SeveralSolnCaseCount) :-
( if CurIndex > EndVal then
MainRows = []
else
construct_fail_row(OutTypes, MainRow, !FailCaseCount),
construct_several_soln_int_lookup_vector(CurIndex + 1, EndVal,
OutTypes, NumOutTypes, [], MoreMainRows,
!.LaterNextRow, !LaterSolnArray,
!FailCaseCount, !OneSolnCaseCount, !SeveralSolnCaseCount),
MainRows = [MainRow | MoreMainRows]
).
construct_several_soln_int_lookup_vector(CurIndex, EndVal,
OutTypes, NumOutTypes, [Index - Soln | Rest], [MainRow | MainRows],
!.LaterNextRow, !LaterSolnArray,
!FailCaseCount, !OneSolnCaseCount, !SeveralSolnCaseCount) :-
( if CurIndex < Index then
construct_fail_row(OutTypes, MainRow, !FailCaseCount),
Remainder = [Index - Soln | Rest]
else
(
Soln = one_soln(OutRvals),
!:OneSolnCaseCount = !.OneSolnCaseCount + 1,
ZeroRval = const(llconst_int(0)),
% The first 0 means there is exactly one solution for this case;
% the second 0 is a dummy that won't be referenced.
MainRow = [ZeroRval, ZeroRval | OutRvals]
;
Soln = several_solns(FirstSolnRvals, LaterSolns),
!:SeveralSolnCaseCount = !.SeveralSolnCaseCount + 1,
list.length(LaterSolns, NumLaterSolns),
FirstRowOffset = !.LaterNextRow * NumOutTypes,
LastRowOffset = (!.LaterNextRow + NumLaterSolns - 1) * NumOutTypes,
FirstRowRval = const(llconst_int(FirstRowOffset)),
LastRowRval = const(llconst_int(LastRowOffset)),
MainRow = [FirstRowRval, LastRowRval | FirstSolnRvals],
!:LaterNextRow = !.LaterNextRow + NumLaterSolns,
!:LaterSolnArray = !.LaterSolnArray ++ cord.from_list(LaterSolns)
),
Remainder = Rest
),
construct_several_soln_int_lookup_vector(CurIndex + 1, EndVal,
OutTypes, NumOutTypes, Remainder, MainRows,
!.LaterNextRow, !LaterSolnArray,
!FailCaseCount, !OneSolnCaseCount, !SeveralSolnCaseCount).
:- pred construct_fail_row(list(llds_type)::in, list(rval)::out,
int::in, int::out) is det.
construct_fail_row(OutTypes, MainRow, !FailCaseCount) :-
% The -1 means no solutions for this case; the 0 is a dummy that
% won't be referenced.
ControlRvals = [const(llconst_int(-1)), const(llconst_int(0))],
% Since this argument (array element) is a place-holder and will never be
% referenced, just fill it in with a dummy entry.
VarRvals = list.map(default_value_for_type, OutTypes),
MainRow = ControlRvals ++ VarRvals,
!:FailCaseCount = !.FailCaseCount + 1.
%-----------------------------------------------------------------------------%
% The bitvector is an array of words (where we use the first 32 bits
% of each word). Each bit represents a tag value for the (range checked)
% input to the lookup switch. The bit is `1' iff we have a case for that
% tag value.
%
:- pred generate_bitvec_test(rval::in, assoc_list(int, T)::in,
int::in, int::in, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.
generate_bitvec_test(IndexRval, CaseVals, Start, _End, CheckCode,
!CI, !CLD) :-
get_globals(!.CI, Globals),
get_word_bits(Globals, WordBits, Log2WordBits),
generate_bit_vec(CaseVals, Start, WordBits, BitVecArgs, BitVecRval, !CI),
% Optimize the single-word case: if all the cases fit into a single word,
% then the word to use is always that word, and the index specifies which
% bit. Otherwise, the high bits of the index specify which word to use
% and the low bits specify which bit.
( if BitVecArgs = [SingleWord] then
Word = SingleWord,
BitNum = IndexRval
else
% This is the same as
% WordNum = binop(int_div, IndexRval, const(llconst_int(WordBits)))
% except that it can generate more efficient code.
WordNum = binop(unchecked_right_shift(int_type_int, shift_by_int),
IndexRval, const(llconst_int(Log2WordBits))),
Word = lval(field(yes(ptag(0u8)), BitVecRval, WordNum)),
% This is the same as
% BitNum = binop(int_mod, IndexRval, const(llconst_int(WordBits)))
% except that it can generate more efficient code.
BitNum = binop(bitwise_and(int_type_int), IndexRval,
const(llconst_int(WordBits - 1)))
),
HasBit = binop(bitwise_and(int_type_int),
binop(unchecked_left_shift(int_type_int, shift_by_int),
const(llconst_int(1)), BitNum),
Word),
fail_if_rval_is_false(HasBit, CheckCode, !CI, !CLD).
% We generate the bitvector by iterating through the cases marking the bit
% for each case. We represent the bitvector here as a map from the word
% number in the vector to the bits for that word.
%
:- pred generate_bit_vec(assoc_list(int, T)::in, int::in, int::in,
list(rval)::out, rval::out, code_info::in, code_info::out) is det.
generate_bit_vec(CaseVals, Start, WordBits, Args, BitVec, !CI) :-
map.init(BitMap0),
generate_bit_vec_2(CaseVals, Start, WordBits, BitMap0, BitMap),
map.to_assoc_list(BitMap, WordVals),
generate_bit_vec_args(WordVals, 0, Args),
add_scalar_static_cell_natural_types(Args, DataAddr, !CI),
BitVec = const(llconst_data_addr(DataAddr, no)).
:- pred generate_bit_vec_2(assoc_list(int, T)::in, int::in, int::in,
map(int, int)::in, map(int, int)::out) is det.
generate_bit_vec_2([], _, _, !BitMap).
generate_bit_vec_2([Tag - _ | Rest], Start, WordBits, !BitMap) :-
Val = Tag - Start,
Word = Val // WordBits,
Offset = Val mod WordBits,
( if map.search(!.BitMap, Word, X0) then
X1 = X0 \/ (1 << Offset)
else
X1 = (1 << Offset)
),
map.set(Word, X1, !BitMap),
generate_bit_vec_2(Rest, Start, WordBits, !BitMap).
:- pred generate_bit_vec_args(list(pair(int))::in, int::in,
list(rval)::out) is det.
generate_bit_vec_args([], _, []).
generate_bit_vec_args([Word - Bits | Rest], Count, [Rval | Rvals]) :-
( if Count < Word then
WordVal = 0,
Remainder = [Word - Bits | Rest]
else
WordVal = Bits,
Remainder = Rest
),
Rval = const(llconst_int(WordVal)),
Count1 = Count + 1,
generate_bit_vec_args(Remainder, Count1, Rvals).
%-----------------------------------------------------------------------------%
default_value_for_type(lt_bool) = const(llconst_int(0)).
default_value_for_type(lt_int_least(_)) = const(llconst_int(0)).
default_value_for_type(lt_int(int_type_int)) = const(llconst_int(0)).
default_value_for_type(lt_int(int_type_uint)) = const(llconst_uint(0u)).
default_value_for_type(lt_int(int_type_int8)) = const(llconst_int8(0i8)).
default_value_for_type(lt_int(int_type_uint8)) = const(llconst_uint8(0u8)).
default_value_for_type(lt_int(int_type_int16)) = const(llconst_int16(0i16)).
default_value_for_type(lt_int(int_type_uint16)) = const(llconst_uint16(0u16)).
default_value_for_type(lt_int(int_type_int32)) = const(llconst_int32(0i32)).
default_value_for_type(lt_int(int_type_uint32)) = const(llconst_uint32(0u32)).
default_value_for_type(lt_int(int_type_int64)) = const(llconst_int64(0i64)).
default_value_for_type(lt_int(int_type_uint64)) = const(llconst_uint64(0u64)).
default_value_for_type(lt_float) = const(llconst_float(0.0)).
default_value_for_type(lt_string) = const(llconst_string("")).
default_value_for_type(lt_data_ptr) = const(llconst_int(0)).
default_value_for_type(lt_code_ptr) = const(llconst_int(0)).
default_value_for_type(lt_word) = const(llconst_int(0)).
%-----------------------------------------------------------------------------%
:- end_module ll_backend.lookup_switch.
%-----------------------------------------------------------------------------%