Files
mercury/compiler/tag_switch.m
Zoltan Somogyi faf96105bb Replace the word_size option with the two options bits_per_word and
Estimated hours taken: 16

options:
	Replace the word_size option with the two options bits_per_word and
	bytes_per_word. The former is needed by lookup_switch, the latter by
	value numbering.

lookup_switch:
	Use the new option instead of word_size.

vn_type, vn_cost, vn_block, value_number:
	Add a new type, vn_params, containing information such as the number
	of bytes per word (from the option) and cost parameters. Use these
	cost parameters to make more realistic decisions.

vn_filter:
	New module to filter out unnecessary uses of temporary variables,
	which gcc does unnecessarily badly on.

value_number, vn_verify:
	Move verification completely to vn_verify. Tighten the verification
	rules relating to tags where it concerns code sequences in which
	the tag of an rval is taken in a statement before an if_val, but
	loosen them to avoid spurious rejections of code sequences containing
	arithmetic comparisons. Fix some missing cases from semidet switches
	that may have lead to overly conservative decisions.

value_number, vn_order:
	Vn_order was making an overly conservative assumption about where
	to split an extended basic block if it couldn't be optimized together.
	Move the decision to value_number and try to make it better. The new
	heuristic is not enabled yet.

vn_debug:
	Change the conditions under which one type of message is printed.

vn_flush:
	Wrap some too long lines.

llds:
	Fix a bug that would prevent profiling from working correctly on
	value numbered code: we weren't scanning instructions inside blocks
	when looking for return addresses.

peephole:
	Enable an optimization previously left disabled by accident.

switch_detection, tag_switch:
	Eliminate an unused argument.
1996-04-16 06:03:45 +00:00

548 lines
18 KiB
Mathematica

%-----------------------------------------------------------------------------%
% Copyright (C) 1995 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.
%-----------------------------------------------------------------------------%
% tag_switch.m - generate switches based on primary and secondary tags.
% Author: zs.
%-----------------------------------------------------------------------------%
:- module tag_switch.
:- interface.
:- import_module hlds_goal, llds, switch_gen, code_info.
:- import_module list, assoc_list.
% Generate intelligent indexing code for tag based switches.
:- pred tag_switch__generate(list(extended_case), var,
code_model, can_fail, label, code_tree, code_info, code_info).
:- mode tag_switch__generate(in, in, in, in, in, out, in, out)
is det.
:- implementation.
:- import_module hlds_module, hlds_data, options, globals, code_gen, code_exprn.
:- import_module map, tree, type_util, std_util, int, require.
% where is the secondary tag (if any) for this primary tag value
:- type stag_loc ---> none ; local ; remote.
% map secondary tag values (-1 stands for none) to their goal
:- type tag_goal_map == map(int, hlds__goal).
:- type tag_goal_list == assoc_list(int, hlds__goal).
% map primary tag values to the set of their goals
:- type tag_case_map == map(tag_bits, pair(stag_loc, tag_goal_map)).
:- type tag_case_list == assoc_list(tag_bits,
pair(stag_loc, tag_goal_map)).
% map primary tag values to the number of constructors sharing them
:- type tag_count_map == map(tag_bits, pair(stag_loc, int)).
:- type tag_count_list == assoc_list(tag_bits, pair(stag_loc, int)).
%-----------------------------------------------------------------------------%
% The idea is to generate two-level switches, first on the primary
% tag and then on the secondary tag. The first-level switch is always
% a chain of if-then-elses, since jump tables are not worthwhile
% unless they have more than about four entries. Second-level switches
% will be either chains of if-then-elses or jump tables depending on
% the number of alternatives sharing the primary tag. Note that we
% should gain performance even if we can generate no jump tables,
% since the two levels reduce the expected number of comparisons,
% and the extraction of the primary and secondary tags is done once
% instead of being repeated for each test.
% We put both the possible tag values and the cases into groups
% depending on their primary tag. We sort the primary tags based
% on the number of alternatives they have. We test the primary tag
% of the switch variable against the possible primary tags starting
% with the most shared primary tag. This arrangement minimizes the
% expected number of primary tag comparisons.
tag_switch__generate(Cases, Var, CodeModel, CanFail, EndLabel, Code) -->
% group the cases based on primary tag value
% and find out how many constructors share each primary tag value
tag_switch__get_tag_counts(Var, TagCountMap),
{ map__to_assoc_list(TagCountMap, TagCountList) },
{ map__init(TagCaseMap0) },
{ tag_switch__group_tags(Cases, TagCaseMap0, TagCaseMap) },
{ tag_switch__order_tags(TagCountList, TagCaseMap, TagCaseList) },
% We get a register for holding the tag. The tag is needed only
% by the switch, and no other code gets control between producing
% the tag value and all uses of it, so we can release the register
% for use by the code of the various cases.
% We forgo using the register if only one primary tag is used,
% or if the "register" we get is likely to be slower than
% recomputing the tag from scratch.
code_info__produce_variable_in_reg(Var, VarCode, VarRval),
code_info__acquire_reg(TagReg),
code_info__release_reg(TagReg),
code_info__get_globals(Globals),
{
TagCaseList = [_, _ | _], % at least two primary tags
globals__lookup_int_option(Globals, num_real_r_regs,
NumRealRegs),
(
NumRealRegs = 0
;
(
TagReg = r(TagRegNo)
;
TagReg = f(_),
error("float reg in tag switch")
),
TagRegNo =< NumRealRegs
)
->
TagCode = node([assign(reg(TagReg), unop(tag, VarRval))
- "compute tag to switch on"]),
TagRval = lval(reg(TagReg))
;
TagCode = empty,
TagRval = unop(tag, VarRval)
},
% we generate FailCode and EndCode here because the last case within
% a primary tag may not be the last case overall
code_info__get_next_label(FailLabel),
(
{ CanFail = cannot_fail },
{ FailCode = empty }
;
{ CanFail = can_fail },
code_info__generate_failure(FailCode1),
{ FailCode = tree(
node([label(FailLabel) - "switch has failed"]),
FailCode1) }
),
tag_switch__generate_primary_tag_codes(TagCaseList,
TagRval, VarRval, CodeModel, CanFail, EndLabel, FailLabel,
TagCountMap, CasesCode),
{ EndCode = node([label(EndLabel) - "end of tag switch"]) },
{ Code = tree(tree(tree(VarCode, TagCode), CasesCode),
tree(FailCode, EndCode)) }.
%-----------------------------------------------------------------------------%
% Generate a series of if-then-elses, one for each primary tag value.
% Jump tables are used only on secondary tags.
:- pred tag_switch__generate_primary_tag_codes(tag_case_list,
rval, rval, code_model, can_fail, label, label,
tag_count_map, code_tree, code_info, code_info).
:- mode tag_switch__generate_primary_tag_codes(in, in, in, in, in, in, in,
in, out, in, out) is det.
tag_switch__generate_primary_tag_codes([], _TagRval, _VarRval, _CodeModel,
_CanFail, _EndLabel, _FailLabel, _TagCountMap, empty) -->
[].
tag_switch__generate_primary_tag_codes([TagGroup | TagGroups],
TagRval, VarRval, CodeModel, CanFail,
EndLabel, FailLabel, TagCountMap, Code) -->
{ TagGroup = Primary - (StagLoc - TagGoalMap) },
{ map__lookup(TagCountMap, Primary, CountInfo) },
{ CountInfo = StagLoc1 - MaxSecondary },
{ StagLoc = StagLoc1 ->
true
;
error("secondary tag locations differ in tag_switch__generate_primary_tag_codes")
},
(
{ TagGroups = [_|_] ; CanFail = can_fail }
->
code_info__grab_code_info(CodeInfo),
code_info__get_next_label(ElseLabel),
{ TestRval = binop(ne, TagRval,
unop(mktag, const(int_const(Primary)))) },
{ TestCode = node([if_val(TestRval, label(ElseLabel))
- "test primary tag only"]) },
tag_switch__generate_primary_tag_code(TagGoalMap,
Primary, MaxSecondary, StagLoc, VarRval, CodeModel,
EndLabel, FailLabel, TagCode),
{ ElseCode = node([
goto(label(EndLabel)) - "skip to end of tag switch",
label(ElseLabel) - "handle next primary tag"]) },
{ ThisTagCode = tree(tree(TestCode, TagCode), ElseCode) },
( { TagGroups = [_|_] } ->
code_info__slap_code_info(CodeInfo)
;
[]
)
;
tag_switch__generate_primary_tag_code(TagGoalMap,
Primary, MaxSecondary, StagLoc, VarRval, CodeModel,
EndLabel, FailLabel, ThisTagCode)
),
tag_switch__generate_primary_tag_codes(TagGroups,
TagRval, VarRval, CodeModel, CanFail, EndLabel, FailLabel,
TagCountMap, OtherTagsCode),
{ Code = tree(ThisTagCode, OtherTagsCode) }.
%-----------------------------------------------------------------------------%
% Generate the code corresponding to a primary tag.
% If this primary tag has secondary tags, decide whether we should
% use a jump table to implement the secondary switch.
:- pred tag_switch__generate_primary_tag_code(tag_goal_map, tag_bits, int,
stag_loc, rval, code_model,
label, label, code_tree, code_info, code_info).
:- mode tag_switch__generate_primary_tag_code(in, in, in, in, in, in,
in, in, out, in, out) is det.
tag_switch__generate_primary_tag_code(GoalMap, Primary, MaxSecondary,
StagLoc, Rval, CodeModel, EndLabel, FailLabel, Code) -->
{ map__to_assoc_list(GoalMap, GoalList) },
(
{ StagLoc = none }
->
( { GoalList = [-1 - Goal] } ->
code_gen__generate_forced_goal(CodeModel, Goal, Code)
;
{ error("more than one goal for non-shared tag") }
)
;
code_info__get_globals(Globals),
{ globals__lookup_int_option(Globals, dense_switch_size,
DenseSwitchSize) },
{ MaxSecondary >= DenseSwitchSize }
->
code_info__acquire_reg(SecTagReg),
code_info__release_reg(SecTagReg),
{ StagLoc = remote ->
SecTagCode = node([assign(reg(SecTagReg),
lval(field(Primary, Rval, const(int_const(0)))))
- "compute remote sec tag to switch on"])
;
SecTagCode = node([assign(reg(SecTagReg),
unop(unmkbody, Rval))
- "compute remote sec tag to switch on"])
},
{ SecTagRval = lval(reg(SecTagReg)) },
(
{ list__length(GoalList, GoalCount) },
{ FullGoalCount is MaxSecondary + 1 },
{ FullGoalCount = GoalCount }
->
{ CanFail = cannot_fail }
;
{ CanFail = can_fail }
),
tag_switch__generate_secondary_tag_chain(GoalList, SecTagRval,
CodeModel, CanFail, EndLabel, FailLabel, Codes),
{ Code = tree(SecTagCode, Codes) }
;
tag_switch__generate_secondary_tag_table(GoalList,
0, MaxSecondary, CodeModel,
EndLabel, FailLabel, Labels, CasesCode),
{ StagLoc = remote ->
Index = lval(field(Primary, Rval, const(int_const(0))))
;
Index = unop(unmkbody, Rval)
},
{ SwitchCode = node([computed_goto(Index, Labels) -
"switch on secondary tag"]) },
{ Code = tree(SwitchCode, CasesCode) }
).
%-----------------------------------------------------------------------------%
% Generate the cases for a primary tag by emitting a chain of
% if-then-elses, the conditions of which check only the secondary tag.
:- pred tag_switch__generate_secondary_tag_chain(tag_goal_list, rval,
code_model, can_fail, label, label, code_tree, code_info, code_info).
:- mode tag_switch__generate_secondary_tag_chain(in, in, in, in, in, in, out,
in, out) is det.
tag_switch__generate_secondary_tag_chain([], _SecTagRval,
_CodeModel, CanFail, _EndLabel, FailLabel, Code) -->
( { CanFail = can_fail } ->
{ Code = node([goto(label(FailLabel))
- "secondary tag does not match"]) }
;
{ Code = empty }
).
tag_switch__generate_secondary_tag_chain([Case0 | Cases0], SecTagRval,
CodeModel, CanFail, EndLabel, FailLabel, Code) -->
{ Case0 = Secondary - Goal },
( { Cases0 = [_|_] ; CanFail = can_fail } ->
code_info__grab_code_info(CodeInfo),
code_info__get_next_label(ElseLabel),
{ TestCode = node([if_val(binop(ne, SecTagRval,
const(int_const(Secondary))),
label(ElseLabel)) - "test remote sec tag only"]) },
code_gen__generate_forced_goal(CodeModel, Goal, GoalCode),
{ ElseCode = node([
goto(label(EndLabel)) - "skip to end of tag switch",
label(ElseLabel) - "handle next secondary tag"]) },
{ ThisCode = tree(TestCode, tree(GoalCode, ElseCode)) },
( { Cases0 = [_|_] } ->
code_info__slap_code_info(CodeInfo)
;
[]
)
;
code_gen__generate_forced_goal(CodeModel, Goal, ThisCode)
),
tag_switch__generate_secondary_tag_chain(Cases0, SecTagRval,
CodeModel, CanFail, EndLabel, FailLabel, OtherCode),
{ Code = tree(ThisCode, OtherCode) }.
%-----------------------------------------------------------------------------%
% Generate the cases for a primary tag using a dense jump table
% that has an entry for all possible secondary tag values.
:- pred tag_switch__generate_secondary_tag_table(tag_goal_list, int, int,
code_model, label, label, list(label), code_tree, code_info, code_info).
:- mode tag_switch__generate_secondary_tag_table(in, in, in,
in, in, in, out, out, in, out) is det.
tag_switch__generate_secondary_tag_table(CaseList, CurSecondary, MaxSecondary,
CodeModel, EndLabel, FailLabel, Labels, Code) -->
( { CurSecondary > MaxSecondary } ->
{ CaseList = [] ->
true
;
error("caselist not empty when reaching limiting secondary tag")
},
{ Labels = [] },
{ Code = empty }
;
{ NextSecondary is CurSecondary + 1 },
( { CaseList = [CurSecondary - Goal | CaseList1] } ->
code_info__get_next_label(NewLabel),
( { CaseList1 = [] } ->
code_gen__generate_forced_goal(CodeModel, Goal,
GoalCode)
;
code_info__grab_code_info(CodeInfo),
code_gen__generate_forced_goal(CodeModel, Goal,
GoalCode),
code_info__slap_code_info(CodeInfo)
),
{ LabelCode = node([label(NewLabel) -
"start of a case in tag switch"]) },
{ GotoCode = node([goto(label(EndLabel)) -
"branch to end of tag switch"]) },
tag_switch__generate_secondary_tag_table(CaseList1,
NextSecondary, MaxSecondary, CodeModel,
EndLabel, FailLabel, OtherLabels, OtherCode),
{ Labels = [NewLabel | OtherLabels] },
{ Code = tree(tree(LabelCode, GoalCode),
tree(GotoCode, OtherCode)) }
;
tag_switch__generate_secondary_tag_table(CaseList,
NextSecondary, MaxSecondary, CodeModel,
EndLabel, FailLabel, OtherLabels, Code),
{ Labels = [FailLabel | OtherLabels] }
)
).
%-----------------------------------------------------------------------------%
% Find out how many secondary tags share each primary tag
% of the given variable.
:- pred tag_switch__get_tag_counts(var, tag_count_map,
code_info, code_info).
:- mode tag_switch__get_tag_counts(in, out, in, out) is det.
tag_switch__get_tag_counts(Var, TagCountMap) -->
code_info__variable_type(Var, Type),
{ type_to_type_id(Type, TypeIdPrime, _) ->
TypeId = TypeIdPrime
;
error("unknown type in tag_switch__get_tag_counts")
},
code_info__get_module_info(ModuleInfo),
{ module_info_types(ModuleInfo, TypeTable) },
{ map__lookup(TypeTable, TypeId, TypeDefn) },
{ TypeDefn = hlds__type_defn(_, _, du_type(_, ConsTable, _), _, _) ->
map__to_assoc_list(ConsTable, ConsList),
tag_switch__cons_list_to_tag_list(ConsList, TagList)
;
error("non-du type in tag_switch__get_tag_counts")
},
{ map__init(TagCountMap0) },
{ tag_switch__get_tag_counts_2(TagList, TagCountMap0, TagCountMap) }.
:- pred tag_switch__get_tag_counts_2(list(cons_tag),
tag_count_map, tag_count_map).
:- mode tag_switch__get_tag_counts_2(in, in, out) is det.
tag_switch__get_tag_counts_2([], TagCountMap, TagCountMap).
tag_switch__get_tag_counts_2([ConsTag | TagList], TagCountMap0, TagCountMap) :-
( ConsTag = simple_tag(Primary) ->
( map__search(TagCountMap0, Primary, _) ->
error("simple tag is shared")
;
map__set(TagCountMap0, Primary, none - (-1),
TagCountMap1)
)
; ConsTag = complicated_tag(Primary, Secondary) ->
( map__search(TagCountMap0, Primary, Target) ->
Target = TagType - MaxSoFar,
( TagType = remote ->
true
;
error("remote tag is shared with non-remote")
),
int__max(Secondary, MaxSoFar, Max),
map__set(TagCountMap0, Primary, remote - Max,
TagCountMap1)
;
map__set(TagCountMap0, Primary, remote - Secondary,
TagCountMap1)
)
; ConsTag = complicated_constant_tag(Primary, Secondary) ->
( map__search(TagCountMap0, Primary, Target) ->
Target = TagType - MaxSoFar,
( TagType = local ->
true
;
error("local tag is shared with non-local")
),
int__max(Secondary, MaxSoFar, Max),
map__set(TagCountMap0, Primary, local - Max,
TagCountMap1)
;
map__set(TagCountMap0, Primary, local - Secondary,
TagCountMap1)
)
;
error("non-du tag in tag_switch__get_tag_counts_2")
),
tag_switch__get_tag_counts_2(TagList, TagCountMap1, TagCountMap).
%-----------------------------------------------------------------------------%
% Group together all the cases that depend on the given variable
% having the same primary tag value.
:- pred tag_switch__group_tags(cases_list, tag_case_map, tag_case_map).
:- mode tag_switch__group_tags(in, in, out) is det.
tag_switch__group_tags([], TagCaseMap, TagCaseMap).
tag_switch__group_tags([Case0 | Cases0], TagCaseMap0, TagCaseMap) :-
Case0 = case(_Priority, Tag, _ConsId, Goal),
( Tag = simple_tag(Primary) ->
( map__search(TagCaseMap0, Primary, _Group) ->
error("simple tag is shared")
;
true
),
map__init(TagGoalMap0),
map__set(TagGoalMap0, -1, Goal, TagGoalMap),
map__set(TagCaseMap0, Primary, none - TagGoalMap, TagCaseMap1)
; Tag = complicated_tag(Primary, Secondary) ->
( map__search(TagCaseMap0, Primary, Group) ->
Group = StagLoc - TagGoalMap0,
( StagLoc = remote ->
true
;
error("remote tag is shared with non-remote")
)
;
map__init(TagGoalMap0)
),
map__set(TagGoalMap0, Secondary, Goal, TagGoalMap),
map__set(TagCaseMap0, Primary, remote - TagGoalMap, TagCaseMap1)
; Tag = complicated_constant_tag(Primary, Secondary) ->
( map__search(TagCaseMap0, Primary, Group) ->
Group = StagLoc - TagGoalMap0,
( StagLoc = local ->
true
;
error("local tag is shared with non-local")
)
;
map__init(TagGoalMap0)
),
map__set(TagGoalMap0, Secondary, Goal, TagGoalMap),
map__set(TagCaseMap0, Primary, local - TagGoalMap, TagCaseMap1)
;
error("non-du tag in tag_switch__group_tags")
),
tag_switch__group_tags(Cases0, TagCaseMap1, TagCaseMap).
%-----------------------------------------------------------------------------%
% Order the most primary tags based on the number of secondary tags
% associated with them. We use selection sort.
% Note that it is not an error for a primary tag to have no case list,
% since this can happen in semideterministic switches.
:- pred tag_switch__order_tags(tag_count_list, tag_case_map, tag_case_list).
:- mode tag_switch__order_tags(in, in, out) is det.
tag_switch__order_tags(TagCountList0, TagCaseMap0, TagCaseList) :-
(
tag_switch__select_frequent_tag(TagCountList0,
Primary, _, TagCountList1)
->
( map__search(TagCaseMap0, Primary, TagCase) ->
map__delete(TagCaseMap0, Primary, TagCaseMap1),
tag_switch__order_tags(TagCountList1, TagCaseMap1,
TagCaseList1),
TagCaseList = [Primary - TagCase | TagCaseList1]
;
tag_switch__order_tags(TagCountList1, TagCaseMap0,
TagCaseList)
)
;
( map__is_empty(TagCaseMap0) ->
TagCaseList = []
;
error("TagCaseMap0 is not empty in tag_switch__order_tags")
)
).
% Select the most frequently used primary tag based on the number of
% secondary tags associated with it.
:- pred tag_switch__select_frequent_tag(tag_count_list, tag_bits, int,
tag_count_list).
:- mode tag_switch__select_frequent_tag(in, out, out, out) is semidet.
tag_switch__select_frequent_tag([TagCount0 | TagCountList1], Primary, Count,
TagCountList) :-
TagCount0 = Primary0 - (_ - Count0),
(
tag_switch__select_frequent_tag(TagCountList1,
Primary1, Count1, TagCountList2),
Count1 > Count0
->
Primary = Primary1,
Count = Count1,
TagCountList = [TagCount0 | TagCountList2]
;
Primary = Primary0,
Count = Count0,
TagCountList = TagCountList1
).
%-----------------------------------------------------------------------------%
:- pred tag_switch__cons_list_to_tag_list(assoc_list(cons_id, cons_tag),
list(cons_tag)).
:- mode tag_switch__cons_list_to_tag_list(in, out) is det.
tag_switch__cons_list_to_tag_list([], []).
tag_switch__cons_list_to_tag_list([_ConsId - ConsTag | ConsList],
[ConsTag | Tagslist]) :-
tag_switch__cons_list_to_tag_list(ConsList, Tagslist).