mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-18 23:35:25 +00:00
Estimated hours taken: 8
Branches: main
Implement a more cache-friendly translation of lookup switches. Previously,
for a switch such as the one in
:- pred p(foo::in, string::out, bar::out, float::out) is semidet.
p(d, "four", f1, 4.4).
p(e, "five", f2, 5.5).
p(f, "six", f4("hex"), 6.6).
p(g, "seven", f5(77.7), 7.7).
we generated three static cells, one for each argument, and then indexed
into each one in turn to get the values of HeadVar__2, HeadVar__3 and
HeadVar__4. The different static cells each represent a column here.
Each of the loads accessing the columns will access a different cache block,
so with this technique we expect to get as many cache misses as there are
output variables.
This diff changes the code we generate to use a vector of static cells
where each cell represents a row. The assignments to the output variables
will now access the different fields of a row, which will be next to each
other. We thus expect only one cache miss irrespective of the number of output
variables, at least up to the number of variables that actually fit into one
cache block.
compiler/global_data.m:
Provide a mechanism for creating not just single (scalar) static cells,
but arrays (vectors) of them.
compiler/lookup_switch.m:
Use the new mechanism to generate code along the lines described above.
Put the information passed between the two halves of the lookup switch
implementation (detection and code generation) into an opaque data
structure.
compiler/switch_gen.m:
Conform to the new interface of lookup_switch.m.
compiler/ll_pseudo_type_info.m:
compiler/stack_layout.m:
compiler/string_switch.m:
compiler/unify_gen.m:
compiler/var_locn.m:
Conform to the change to global_data.m.
compiler/llds.m:
Define the data structures for holding vectors of static cells. Rename
the function symbols we used to use to refer to static cells to make
clear that they apply to scalar cells only. Provide similar mechanisms
for representing static cell vectors and references to them.
Generalize heap_ref heap references to allow the index to be computed
at runtime, not compile time. For symmetry's sake, do likewise
for stack references.
compiler/llds_out.m:
Add the code required to write out static cell vectors.
Rename decl_ids to increase clarity and avoid ambiguity.
compiler/code_util.m:
compiler/exprn_aux.m:
Modify code that traverses rvals to now also traverse the new rvals
inside memory references.
compiler/name_mangle.m:
Provide the prefix for static cell vectors.
compiler/layout_out.m:
compiler/rtti_out.m:
compiler/opt_debug.m:
Conform to the change to data_addrs and decl_ids.
compiler/code_info.m:
Provide access to the new functionality in global_data.m, and conform
to the change to llds.m.
Provide a utility predicate needed by lookup_switch.m.
compiler/hlds_llds.m:
Fix the formatting of some comments.
tools/binary:
tools/binary_step:
Fix the bit rot that has set in since they were last used (the rest
of the system has changed quite a lot since then). I had to do so
to debug one part of this change.
tests/hard_coded/dense_lookup_switch2.{m,exp}:
tests/hard_coded/dense_lookup_switch3.{m,exp}:
New test cases to exercise the new algorithm.
tests/hard_coded/Mmakefile:
Enable the new test cases, as well as an old one (from 1997!)
that seems never to have been enabled.
224 lines
8.9 KiB
Mathematica
224 lines
8.9 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1994-2006 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: string_switch.m.
|
|
% Author: fjh.
|
|
|
|
% For switches on strings, we generate a hash table using open addressing
|
|
% to resolve hash conflicts.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module ll_backend.string_switch.
|
|
:- interface.
|
|
|
|
:- import_module backend_libs.switch_util.
|
|
:- import_module hlds.code_model.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module ll_backend.code_info.
|
|
:- import_module ll_backend.llds.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- pred generate_string_switch(cases_list::in, prog_var::in, code_model::in,
|
|
can_fail::in, hlds_goal_info::in, label::in,
|
|
branch_end::in, branch_end::out, code_tree::out,
|
|
code_info::in, code_info::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module backend_libs.builtin_ops.
|
|
:- import_module hlds.hlds_data.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_llds.
|
|
:- import_module libs.compiler_util.
|
|
:- import_module libs.tree.
|
|
:- import_module ll_backend.code_gen.
|
|
:- import_module ll_backend.trace.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module bool.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module string.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
generate_string_switch(Cases, Var, CodeModel, _CanFail, SwitchGoalInfo,
|
|
EndLabel, !MaybeEnd, Code, !CI) :-
|
|
code_info.produce_variable(Var, VarCode, VarRval, !CI),
|
|
code_info.acquire_reg(r, SlotReg, !CI),
|
|
code_info.acquire_reg(r, StringReg, !CI),
|
|
code_info.get_next_label(LoopLabel, !CI),
|
|
code_info.get_next_label(FailLabel, !CI),
|
|
code_info.get_next_label(JumpLabel, !CI),
|
|
|
|
% Determine how big to make the hash table. Currently we round the number
|
|
% of cases up to the nearest power of two, and then double it.
|
|
% This should hopefully ensure that we don't get too many hash collisions.
|
|
|
|
list.length(Cases, NumCases),
|
|
int.log2(NumCases, LogNumCases),
|
|
int.pow(2, LogNumCases, RoundedNumCases),
|
|
TableSize = 2 * RoundedNumCases,
|
|
HashMask = TableSize - 1,
|
|
|
|
% Compute the hash table.
|
|
switch_util.string_hash_cases(Cases, HashMask, HashValsMap),
|
|
map.to_assoc_list(HashValsMap, HashValsList),
|
|
switch_util.calc_hash_slots(HashValsList, HashValsMap, HashSlotsMap),
|
|
|
|
% Note that it is safe to release the registers now, even though we haven't
|
|
% yet generated all the code which uses them, because that code will be
|
|
% executed before the code for the cases (which might reuse those
|
|
% registers), and because that code is generated manually (below)
|
|
% so we don't need the reg info to be valid when we generate it.
|
|
|
|
code_info.release_reg(SlotReg, !CI),
|
|
code_info.release_reg(StringReg, !CI),
|
|
|
|
% Generate the code for when the hash lookup fails. This must be done
|
|
% before gen_hash_slots, since we want to use the exprn_info corresponding
|
|
% to the start of the switch, not to the end of the last case.
|
|
code_info.generate_failure(FailCode, !CI),
|
|
|
|
% Generate the code etc. for the hash table.
|
|
gen_hash_slots(0, TableSize, HashSlotsMap, CodeModel, SwitchGoalInfo,
|
|
FailLabel, EndLabel, !MaybeEnd, Strings, Labels, NextSlots,
|
|
SlotsCode, !CI),
|
|
|
|
% Generate code which does the hash table lookup
|
|
(
|
|
add_scalar_static_cell_natural_types(NextSlots, NextSlotsTableAddr,
|
|
!CI),
|
|
NextSlotsTable = const(data_addr_const(NextSlotsTableAddr, no)),
|
|
add_scalar_static_cell_natural_types(Strings, StringTableAddr, !CI),
|
|
StringTable = const(data_addr_const(StringTableAddr, no)),
|
|
HashLookupCode = node([
|
|
comment("hashed string switch") - "",
|
|
assign(SlotReg,
|
|
binop(bitwise_and, unop(hash_string, VarRval),
|
|
const(int_const(HashMask))))
|
|
- "compute the hash value of the input string",
|
|
label(LoopLabel) - "begin hash chain loop",
|
|
assign(StringReg,
|
|
binop(array_index(elem_type_string),
|
|
StringTable, lval(SlotReg)))
|
|
- "lookup the string for this hash slot",
|
|
if_val(binop(logical_and, lval(StringReg),
|
|
binop(str_eq, lval(StringReg), VarRval)), label(JumpLabel))
|
|
- "did we find a match?",
|
|
assign(SlotReg,
|
|
binop(array_index(elem_type_int),
|
|
NextSlotsTable, lval(SlotReg)))
|
|
- "not yet, so get next slot in hash chain",
|
|
if_val(binop(int_ge, lval(SlotReg), const(int_const(0))),
|
|
label(LoopLabel))
|
|
- "keep searching until we reach the end of the chain",
|
|
label(FailLabel) - "no match, so fail"
|
|
])
|
|
),
|
|
JumpCode = node([
|
|
label(JumpLabel) - "we found a match",
|
|
computed_goto(lval(SlotReg), Labels) - "jump to the corresponding code"
|
|
]),
|
|
Code = tree_list([VarCode, HashLookupCode, FailCode, JumpCode, SlotsCode]).
|
|
|
|
:- pred gen_hash_slots(int::in, int::in,
|
|
map(int, hash_slot)::in, code_model::in, hlds_goal_info::in, label::in,
|
|
label::in, branch_end::in, branch_end::out,
|
|
list(rval)::out, list(label)::out, list(rval)::out, code_tree::out,
|
|
code_info::in, code_info::out) is det.
|
|
|
|
gen_hash_slots(Slot, TableSize, HashSlotMap, CodeModel, SwitchGoalInfo,
|
|
FailLabel, EndLabel, !MaybeEnd, Strings, Labels, NextSlots,
|
|
Code, !CI) :-
|
|
( Slot = TableSize ->
|
|
Strings = [],
|
|
Labels = [],
|
|
NextSlots = [],
|
|
Code = node([label(EndLabel) - "end of hashed string switch"])
|
|
;
|
|
gen_hash_slot(Slot, TableSize, HashSlotMap, CodeModel, SwitchGoalInfo,
|
|
FailLabel, EndLabel, !MaybeEnd, String, Label, NextSlot,
|
|
SlotCode, !CI),
|
|
Slot1 = Slot + 1,
|
|
gen_hash_slots(Slot1, TableSize, HashSlotMap, CodeModel,
|
|
SwitchGoalInfo, FailLabel, EndLabel, !MaybeEnd, Strings0, Labels0,
|
|
NextSlots0, Code0, !CI),
|
|
Strings = [String | Strings0],
|
|
Labels = [Label | Labels0],
|
|
NextSlots = [NextSlot | NextSlots0],
|
|
Code = tree(SlotCode, Code0)
|
|
).
|
|
|
|
:- pred gen_hash_slot(int::in, int::in, map(int, hash_slot)::in,
|
|
code_model::in, hlds_goal_info::in, label::in, label::in,
|
|
branch_end::in, branch_end::out, rval::out, label::out, rval::out,
|
|
code_tree::out, code_info::in, code_info::out) is det.
|
|
|
|
gen_hash_slot(Slot, TblSize, HashSlotMap, CodeModel, SwitchGoalInfo, FailLabel,
|
|
EndLabel, !MaybeEnd, StringRval, Label, NextSlotRval, Code, !CI) :-
|
|
( map.search(HashSlotMap, Slot, hash_slot(Case, Next)) ->
|
|
NextSlotRval = const(int_const(Next)),
|
|
Case = case(_, ConsTag, _, Goal),
|
|
( ConsTag = string_constant(String0) ->
|
|
String = String0
|
|
;
|
|
unexpected(this_file, "gen_hash_slots: string expected")
|
|
),
|
|
StringRval = const(string_const(String)),
|
|
code_info.get_next_label(Label, !CI),
|
|
string.append_list(["case """, String, """"], Comment),
|
|
LabelCode = node([label(Label) - Comment]),
|
|
code_info.remember_position(!.CI, BranchStart),
|
|
trace.maybe_generate_internal_event_code(Goal, SwitchGoalInfo,
|
|
TraceCode, !CI),
|
|
code_gen.generate_goal(CodeModel, Goal, GoalCode, !CI),
|
|
goal_info_get_store_map(SwitchGoalInfo, StoreMap),
|
|
code_info.generate_branch_end(StoreMap, !MaybeEnd, SaveCode, !CI),
|
|
( this_is_last_case(Slot, TblSize, HashSlotMap) ->
|
|
true
|
|
;
|
|
code_info.reset_to_position(BranchStart, !CI)
|
|
),
|
|
FinishCode = node([goto(label(EndLabel)) - "jump to end of switch"]),
|
|
Code = tree_list([LabelCode, TraceCode, GoalCode, SaveCode,
|
|
FinishCode])
|
|
;
|
|
StringRval = const(int_const(0)),
|
|
Label = FailLabel,
|
|
NextSlotRval = const(int_const(-2)),
|
|
Code = empty
|
|
).
|
|
|
|
:- pred this_is_last_case(int::in, int::in, map(int, hash_slot)::in)
|
|
is semidet.
|
|
|
|
this_is_last_case(Slot, TableSize, Table) :-
|
|
Slot1 = Slot + 1,
|
|
( Slot1 >= TableSize ->
|
|
true
|
|
;
|
|
\+ map.contains(Table, Slot1),
|
|
this_is_last_case(Slot1, TableSize, Table)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func this_file = string.
|
|
|
|
this_file = "string_switch.m".
|
|
|
|
%-----------------------------------------------------------------------------%
|