%-----------------------------------------------------------------------------% % Copyright (C) 1994-1998 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. %-----------------------------------------------------------------------------% % tag_switch.m - generate switches based on primary and secondary tags. % Author: zs. %-----------------------------------------------------------------------------% :- module tag_switch. :- interface. :- import_module hlds_goal, hlds_data, llds, switch_gen, code_info. :- import_module list, term. % Generate intelligent indexing code for tag based switches. :- pred tag_switch__generate(list(extended_case), var, code_model, can_fail, store_map, label, code_tree, code_info, code_info). :- mode tag_switch__generate(in, in, in, in, in, in, out, in, out) is det. :- implementation. :- import_module hlds_module, hlds_pred, code_gen, trace. :- import_module options, globals, type_util, prog_data. :- import_module assoc_list, map, tree, bool, int, string. :- import_module require, std_util. % 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 stag_goal_map == map(int, hlds_goal). :- type stag_goal_list == assoc_list(int, hlds_goal). % map primary tag values to the set of their goals :- type ptag_case_map == map(tag_bits, pair(stag_loc, stag_goal_map)). :- type ptag_case_list == assoc_list(tag_bits, pair(stag_loc, stag_goal_map)). % map primary tag values to the number of constructors sharing them :- type ptag_count_map == map(tag_bits, pair(stag_loc, int)). :- type ptag_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. Since more than one function % symbol can be eliminated by a failed primary tag test, this reduces % the expected the number of comparisons required before finding the % code corresponding to the actual value of the switch variable. % We also get a speedup compared to non-tag switches by extracting % the primary and secondary tags once instead of repeatedly for % each functor test. % % We have four methods we can use for generating the code for the % switches on both primary and secondary tags. % % 1. try-me-else chains have the form % % if (tag(var) != tag1) goto L1 % code for tag1 % goto end % L1: if (tag(var) != tag2) goto L2 % code for tag2 % goto end % L2: ... % Ln: code for last possible tag value (or failure) % goto end % % 2. try chains have the form % % if (tag(var) == tag1) goto L1 % if (tag(var) == tag2) goto L2 % ... % code for last possible tag value (or failure) % goto end % L1: code for tag1 % goto end % L2: code for tag2 % goto end % ... % % 3. jump tables have the form % % goto tag(var) of L1, L2, ... % L1: code for tag1 % goto end % L2: code for tag2 % goto end % ... % % 4. binary search switches have the form % % if (tag(var)) > 1) goto L23 % if (tag(var)) != 0) goto L1 % code for tag 0 % goto end % L1: code for tag 1 % goto end % L23: if (tag(var)) != 2) goto L3 % code for tag 2 % goto end % L3: code for tag 3 % goto end % % Note that for a det switch with two tag values, try-me-else chains % and try chains are equivalent. % Which method is best depends on the number of possible tag values, % on the costs of taken/untaken branches and table lookups on the given % architecture, and on the frequency with which the various % alternatives are taken. % % While the first two are in principle known at compile time, % the third is not. Nevertheless, for switches on primary tags % we can use the heuristic that the more secondary tags assigned to % a primary tag, the more likely that the switch variable will have % that primary tag at runtime. % % Try chains are good for switches with small numbers of alternatives % on architectures where untaken branches are cheaper than taken % branches. % % Try-me-else chains are good for switches with very small numbers of % alternatives on architectures where taken branches are cheaper than % untaken branches (which are rare these days). % % Jump tables are good for switches with large numbers of alternatives. % The cost of jumping through a jump table is relatively high, since % it involves a memory access and an indirect branch (which most % current architectures do not handle well), but this cost is % independent of the number of alternatives. % % Binary search switches are good for switches where the number of % alternatives is large enough for the reduced expected number of % branches executed to overcome the extra overhead of the subtraction % required for some conditional branches (compared to try chains % and try-me-else chains), but not large enough to make the % expected cost of the expected number of comparisons exceed the % expected cost of a jump table lookup and dispatch. % For try-me-else chains, we want tag1 to be the most frequent case, % tag 2 the next most frequent case, etc. % % For det try chains, we want the last tag value to be the most % frequent case, since it can be reached without taken jumps. % We want tag1 to be the next most frequent, tag2 the next most % frequent after that, etc. % % For semidet try chains, there is no last possible tag value (the % code for failure occupies its position), so we want tag1 to be % the most frequent case, tag 2 the next most frequent case, etc. % % For jump tables, the position of the labels in the computed goto % must conform to their numerical value. The order of the code % fragments does not really matter, although the last has a slight % edge in that no goto is needed to reach the code following the % switch. If there is no code following the switch (which happens % very frequently), then even this advantage is nullified. % % For binary search switches, we want the case of the most frequently % occurring tag to be the first, since this code is reached with no % taken branches and ends with an unconditional branch, whereas % reaching the code of the other cases requires at least one taken % *conditional* branch. In general, at each binary decision we % want the more frequently reached cases to be in the half that % immediately follows the if statement implementing the decision. :- type switch_method ---> try_me_else_chain ; try_chain ; jump_table ; binary_search. tag_switch__generate(Cases, Var, CodeModel, CanFail, StoreMap, EndLabel, Code) --> % group the cases based on primary tag value % and find out how many constructors share each primary tag value code_info__get_module_info(ModuleInfo), code_info__get_proc_info(ProcInfo), { proc_info_vartypes(ProcInfo, VarTypes) }, { map__lookup(VarTypes, Var, Type) }, { tag_switch__get_ptag_counts(Type, ModuleInfo, MaxPrimary, PtagCountMap) }, { map__to_assoc_list(PtagCountMap, PtagCountList) }, { map__init(PtagCaseMap0) }, { tag_switch__group_cases_by_ptag(Cases, PtagCaseMap0, PtagCaseMap) }, { map__count(PtagCaseMap, PtagsUsed) }, code_info__get_globals(Globals), { globals__lookup_int_option(Globals, dense_switch_size, DenseSwitchSize) }, { globals__lookup_int_option(Globals, try_switch_size, TrySwitchSize) }, { globals__lookup_int_option(Globals, binary_switch_size, BinarySwitchSize) }, ( { PtagsUsed >= DenseSwitchSize } -> { PrimaryMethod = jump_table } ; { PtagsUsed >= BinarySwitchSize } -> { PrimaryMethod = binary_search } ; { PtagsUsed >= TrySwitchSize } -> { PrimaryMethod = try_chain } ; { PrimaryMethod = try_me_else_chain } ), % 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 the primary tag is needed only once, % 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(r, PtagReg), code_info__release_reg(PtagReg), { PrimaryMethod \= jump_table, PtagsUsed >= 2, globals__lookup_int_option(Globals, num_real_r_regs, NumRealRegs), ( NumRealRegs = 0 ; ( PtagReg = reg(r, PtagRegNo) -> PtagRegNo =< NumRealRegs ; error("improper reg in tag switch") ) ) -> PtagCode = node([ assign(PtagReg, unop(tag, VarRval)) - "compute tag to switch on" ]), PtagRval = lval(PtagReg) ; PtagCode = empty, PtagRval = 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), { FailLabelCode = node([ label(FailLabel) - "switch has failed" ]) }, ( { CanFail = cannot_fail }, { FailCode = node([ goto(do_not_reached) - "oh-oh, det switch failed" ]) } ; { CanFail = can_fail }, code_info__generate_failure(FailCode) ), { LabelledFailCode = tree(FailLabelCode, FailCode) }, { EndCode = node([label(EndLabel) - "end of tag switch"]) }, ( { PrimaryMethod = binary_search }, { tag_switch__order_ptags_by_value(0, MaxPrimary, PtagCaseMap, PtagCaseList) }, tag_switch__generate_primary_binary_search(PtagCaseList, 0, MaxPrimary, PtagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, no, MaybeFinalCodeInfo, CasesCode), ( { MaybeFinalCodeInfo = yes(FinalCodeInfo) } -> code_info__slap_code_info(FinalCodeInfo) ; { error("binary search switch has no useful cases") } ) ; { PrimaryMethod = jump_table }, { tag_switch__order_ptags_by_value(0, MaxPrimary, PtagCaseMap, PtagCaseList) }, tag_switch__generate_primary_jump_table(PtagCaseList, 0, MaxPrimary, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, PtagCountMap, Labels, TableCode), { SwitchCode = node([ computed_goto(PtagRval, Labels) - "switch on primary tag" ]) }, { CasesCode = tree(SwitchCode, TableCode) } ; { PrimaryMethod = try_chain }, { tag_switch__order_ptags_by_count(PtagCountList, PtagCaseMap, PtagCaseList0) }, { CanFail = cannot_fail, PtagCaseList0 = [MostFreqCase | OtherCases] -> list__append(OtherCases, [MostFreqCase], PtagCaseList) ; PtagCaseList = PtagCaseList0 }, tag_switch__generate_primary_try_chain(PtagCaseList, PtagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, empty, empty, CasesCode) ; { PrimaryMethod = try_me_else_chain }, { tag_switch__order_ptags_by_count(PtagCountList, PtagCaseMap, PtagCaseList) }, tag_switch__generate_primary_try_me_else_chain(PtagCaseList, PtagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, CasesCode) ), { Code = tree(VarCode, tree(PtagCode, tree(CasesCode, tree(LabelledFailCode, EndCode)))) }. %-----------------------------------------------------------------------------% % Generate a switch on a primary tag value using a try-me-else chain. :- pred tag_switch__generate_primary_try_me_else_chain(ptag_case_list, rval, rval, code_model, can_fail, store_map, label, label, ptag_count_map, code_tree, code_info, code_info). :- mode tag_switch__generate_primary_try_me_else_chain(in, in, in, in, in, in, in, in, in, out, in, out) is det. tag_switch__generate_primary_try_me_else_chain([], _, _, _, _, _, _, _, _, _) --> { error("generate_primary_try_me_else_chain: empty switch") }. tag_switch__generate_primary_try_me_else_chain([PtagGroup | PtagGroups], TagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, Code) --> { PtagGroup = Primary - (StagLoc - StagGoalMap) }, { map__lookup(PtagCountMap, Primary, CountInfo) }, { CountInfo = StagLoc1 - MaxSecondary }, { StagLoc = StagLoc1 -> true ; error("secondary tag locations differ in generate_primary_try_me_else_chain") }, ( { PtagGroups = [_|_] ; 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(StagGoalMap, Primary, MaxSecondary, StagLoc, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, TagCode), { ElseCode = node([ label(ElseLabel) - "handle next primary tag" ]) }, { ThisTagCode = tree(TestCode, tree(TagCode, ElseCode)) }, ( { PtagGroups = [_|_] } -> code_info__slap_code_info(CodeInfo), tag_switch__generate_primary_try_me_else_chain( PtagGroups, TagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, OtherTagsCode), { Code = tree(ThisTagCode, OtherTagsCode) } ; % FailLabel ought to be the next label anyway, % so this goto will be optimized away (unless the % layout of the failcode in the caller changes). { FailCode = node([ goto(label(FailLabel)) - "primary tag with no code to handle it" ]) }, { Code = tree(ThisTagCode, FailCode) } ) ; tag_switch__generate_primary_tag_code(StagGoalMap, Primary, MaxSecondary, StagLoc, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, ThisTagCode), { Code = ThisTagCode } ). %-----------------------------------------------------------------------------% % Generate a switch on a primary tag value using a try chain. :- pred tag_switch__generate_primary_try_chain(ptag_case_list, rval, rval, code_model, can_fail, store_map, label, label, ptag_count_map, code_tree, code_tree, code_tree, code_info, code_info). :- mode tag_switch__generate_primary_try_chain(in, in, in, in, in, in, in, in, in, in, in, out, in, out) is det. tag_switch__generate_primary_try_chain([], _, _, _, _, _, _, _, _, _, _, _) --> { error("empty list in generate_primary_try_chain") }. tag_switch__generate_primary_try_chain([PtagGroup | PtagGroups], TagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, PrevTests0, PrevCases0, Code) --> { PtagGroup = Primary - (StagLoc - StagGoalMap) }, { map__lookup(PtagCountMap, Primary, CountInfo) }, { CountInfo = StagLoc1 - MaxSecondary }, { StagLoc = StagLoc1 -> true ; error("secondary tag locations differ in generate_primary_try_chain") }, ( { PtagGroups = [_|_] ; CanFail = can_fail } -> code_info__grab_code_info(CodeInfo), code_info__get_next_label(ThisPtagLabel), { TestRval = binop(eq, TagRval, unop(mktag, const(int_const(Primary)))) }, { TestCode = node([ if_val(TestRval, label(ThisPtagLabel)) - "test primary tag only" ]) }, { LabelCode = node([ label(ThisPtagLabel) - "this primary tag" ]) }, tag_switch__generate_primary_tag_code(StagGoalMap, Primary, MaxSecondary, StagLoc, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, TagCode), { PrevTests = tree(PrevTests0, TestCode) }, { PrevCases = tree(tree(LabelCode, TagCode), PrevCases0) }, ( { PtagGroups = [_|_] } -> code_info__slap_code_info(CodeInfo), tag_switch__generate_primary_try_chain(PtagGroups, TagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, PrevTests, PrevCases, Code) ; { FailCode = node([ goto(label(FailLabel)) - "primary tag with no code to handle it" ]) }, { Code = tree(PrevTests, tree(FailCode, PrevCases)) } ) ; { Comment = node([ comment("fallthrough to last tag value") - "" ]) }, tag_switch__generate_primary_tag_code(StagGoalMap, Primary, MaxSecondary, StagLoc, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, TagCode), { Code = tree(PrevTests0, tree(Comment, tree(TagCode, PrevCases0))) } ). %-----------------------------------------------------------------------------% % Generate the cases for a primary tag using a dense jump table % that has an entry for all possible primary tag values. :- pred tag_switch__generate_primary_jump_table(ptag_case_list, int, int, rval, code_model, store_map, label, label, ptag_count_map, list(label), code_tree, code_info, code_info). :- mode tag_switch__generate_primary_jump_table(in, in, in, in, in, in, in, in, in, out, out, in, out) is det. tag_switch__generate_primary_jump_table(PtagGroups, CurPrimary, MaxPrimary, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, PtagCountMap, Labels, Code) --> ( { CurPrimary > MaxPrimary } -> { PtagGroups = [] -> true ; error("caselist not empty when reaching limiting primary tag") }, { Labels = [] }, { Code = empty } ; { NextPrimary is CurPrimary + 1 }, ( { PtagGroups = [CurPrimary - PrimaryInfo | PtagGroups1] } -> { PrimaryInfo = StagLoc - StagGoalMap }, { map__lookup(PtagCountMap, CurPrimary, CountInfo) }, { CountInfo = StagLoc1 - MaxSecondary }, { StagLoc = StagLoc1 -> true ; error("secondary tag locations differ in generate_primary_jump_table") }, code_info__get_next_label(NewLabel), { LabelCode = node([ label(NewLabel) - "start of a case in primary tag switch" ]) }, ( { PtagGroups1 = [] } -> tag_switch__generate_primary_tag_code( StagGoalMap, CurPrimary, MaxSecondary, StagLoc, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, ThisTagCode) ; code_info__grab_code_info(CodeInfo), tag_switch__generate_primary_tag_code( StagGoalMap, CurPrimary, MaxSecondary, StagLoc, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, ThisTagCode), code_info__slap_code_info(CodeInfo) ), tag_switch__generate_primary_jump_table(PtagGroups1, NextPrimary, MaxPrimary, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, PtagCountMap, OtherLabels, OtherCode), { Labels = [NewLabel | OtherLabels] }, { Code = tree(LabelCode, tree(ThisTagCode, OtherCode)) } ; tag_switch__generate_primary_jump_table(PtagGroups, NextPrimary, MaxPrimary, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, PtagCountMap, OtherLabels, Code), { Labels = [FailLabel | OtherLabels] } ) ). %-----------------------------------------------------------------------------% % Generate the cases for a primary tag using a binary search. % This invocation looks after primary tag values in the range % MinPtag to MaxPtag (including both boundary values). :- pred tag_switch__generate_primary_binary_search(ptag_case_list, int, int, rval, rval, code_model, can_fail, store_map, label, label, ptag_count_map, maybe(code_info), maybe(code_info), code_tree, code_info, code_info). :- mode tag_switch__generate_primary_binary_search(in, in, in, in, in, in, in, in, in, in, in, in, out, out, in, out) is det. tag_switch__generate_primary_binary_search(PtagGroups, MinPtag, MaxPtag, PtagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, MaybeFinalCodeInfo0, MaybeFinalCodeInfo, Code) --> ( { MinPtag = MaxPtag } -> { CurPrimary = MinPtag }, ( { PtagGroups = [] } -> % there is no code for this tag ( { CanFail = can_fail }, { string__int_to_string(CurPrimary, PtagStr) }, { string__append("no code for ptag ", PtagStr, Comment) }, { Code = node([ goto(label(FailLabel)) - Comment ]) } ; { CanFail = cannot_fail }, { Code = empty } ), { MaybeFinalCodeInfo = MaybeFinalCodeInfo0 } ; { PtagGroups = [CurPrimary - PrimaryInfo] } -> { PrimaryInfo = StagLoc - StagGoalMap }, { map__lookup(PtagCountMap, CurPrimary, CountInfo) }, { CountInfo = StagLoc1 - MaxSecondary }, { StagLoc = StagLoc1 -> true ; error("secondary tag locations differ in generate_primary_jump_table") }, tag_switch__generate_primary_tag_code( StagGoalMap, CurPrimary, MaxSecondary, StagLoc, VarRval, CodeModel, StoreMap, EndLabel, FailLabel, Code), code_info__grab_code_info(CodeInfo), { MaybeFinalCodeInfo = yes(CodeInfo) } ; { error("caselist not singleton or empty when binary search ends") } ) ; { LowRangeEnd is (MinPtag + MaxPtag) // 2 }, { HighRangeStart is LowRangeEnd + 1 }, { InLowGroup = lambda([PtagGroup::in] is semidet, ( PtagGroup = Ptag - _, Ptag =< LowRangeEnd )) }, { list__filter(InLowGroup, PtagGroups, LowGroups, HighGroups) }, code_info__get_next_label(NewLabel), { string__int_to_string(MinPtag, LowStartStr) }, { string__int_to_string(LowRangeEnd, LowEndStr) }, { string__int_to_string(HighRangeStart, HighStartStr) }, { string__int_to_string(MaxPtag, HighEndStr) }, { string__append_list(["fallthrough for ptags ", LowStartStr, " to ", LowEndStr], IfComment) }, { string__append_list(["code for ptags ", HighStartStr, " to ", HighEndStr], LabelComment) }, { LowRangeEndConst = const(int_const(LowRangeEnd)) }, { TestRval = binop(>, PtagRval, LowRangeEndConst) }, { IfCode = node([ if_val(TestRval, label(NewLabel)) - IfComment ]) }, { LabelCode = node([ label(NewLabel) - LabelComment ]) }, code_info__grab_code_info(CodeInfo), tag_switch__generate_primary_binary_search(LowGroups, MinPtag, LowRangeEnd, PtagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, MaybeFinalCodeInfo0, MaybeFinalCodeInfo1, LowRangeCode), code_info__slap_code_info(CodeInfo), tag_switch__generate_primary_binary_search(HighGroups, HighRangeStart, MaxPtag, PtagRval, VarRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PtagCountMap, MaybeFinalCodeInfo1, MaybeFinalCodeInfo, HighRangeCode), { Code = tree(IfCode, tree(LowRangeCode, tree(LabelCode, HighRangeCode))) } ). %-----------------------------------------------------------------------------% % 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(stag_goal_map, tag_bits, int, stag_loc, rval, code_model, store_map, label, label, code_tree, code_info, code_info). :- mode tag_switch__generate_primary_tag_code(in, 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, StoreMap, EndLabel, FailLabel, Code) --> { map__to_assoc_list(GoalMap, GoalList) }, ( { StagLoc = none } -> % There is no secondary tag, so there is no switch on it ( { GoalList = [-1 - Goal] } -> trace__maybe_generate_internal_event_code(Goal, TraceCode), code_gen__generate_goal(CodeModel, Goal, GoalCode), code_info__generate_branch_end(CodeModel, StoreMap, SaveCode), { GotoCode = node([ goto(label(EndLabel)) - "skip to end of primary tag switch" ]) }, { Code = tree(TraceCode, tree(GoalCode, tree(SaveCode, GotoCode))) } ; { GoalList = [] } -> { error("no goal for non-shared tag") } ; { error("more than one goal for non-shared tag") } ) ; % There is a secondary tag, so figure out how to switch on it code_info__get_globals(Globals), { globals__lookup_int_option(Globals, dense_switch_size, DenseSwitchSize) }, { globals__lookup_int_option(Globals, binary_switch_size, BinarySwitchSize) }, { globals__lookup_int_option(Globals, try_switch_size, TrySwitchSize) }, { MaxSecondary >= DenseSwitchSize -> SecondaryMethod = jump_table ; MaxSecondary >= BinarySwitchSize -> SecondaryMethod = binary_search ; MaxSecondary >= TrySwitchSize -> SecondaryMethod = try_chain ; SecondaryMethod = try_me_else_chain }, { StagLoc = remote -> OrigStagRval = lval(field(yes(Primary), Rval, const(int_const(0)))), Comment = "compute remote sec tag to switch on" ; OrigStagRval = unop(unmkbody, Rval), Comment = "compute local sec tag to switch on" }, code_info__acquire_reg(r, StagReg), code_info__release_reg(StagReg), { SecondaryMethod \= jump_table, MaxSecondary >= 2, globals__lookup_int_option(Globals, num_real_r_regs, NumRealRegs), ( NumRealRegs = 0 ; ( StagReg = reg(r, StagRegNo) -> StagRegNo =< NumRealRegs ; error("improper reg in tag switch") ) ) -> StagCode = node([ assign(StagReg, OrigStagRval) - Comment ]), StagRval = lval(StagReg) ; StagCode = empty, StagRval = OrigStagRval }, ( { list__length(GoalList, GoalCount) }, { FullGoalCount is MaxSecondary + 1 }, { FullGoalCount = GoalCount } -> { CanFail = cannot_fail } ; { CanFail = can_fail } ), ( { SecondaryMethod = jump_table }, tag_switch__generate_secondary_jump_table(GoalList, 0, MaxSecondary, CodeModel, StoreMap, EndLabel, FailLabel, Labels, CasesCode), { SwitchCode = node([ computed_goto(StagRval, Labels) - "switch on secondary tag" ]) }, { Code = tree(SwitchCode, CasesCode) } ; { SecondaryMethod = binary_search }, tag_switch__generate_secondary_binary_search(GoalList, 0, MaxSecondary, StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, no, MaybeFinalCodeInfo, Code), ( { MaybeFinalCodeInfo = yes(FinalCodeInfo) } -> code_info__slap_code_info(FinalCodeInfo) ; { error("binary search switch has no useful cases") } ) ; { SecondaryMethod = try_chain }, tag_switch__generate_secondary_try_chain(GoalList, StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, empty, empty, Codes), { Code = tree(StagCode, Codes) } ; { SecondaryMethod = try_me_else_chain }, tag_switch__generate_secondary_try_me_else_chain( GoalList, StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, Codes), { Code = tree(StagCode, Codes) } ) ). %-----------------------------------------------------------------------------% % Generate a switch on a secondary tag value using a try-me-else chain. :- pred tag_switch__generate_secondary_try_me_else_chain(stag_goal_list, rval, code_model, can_fail, store_map, label, label, code_tree, code_info, code_info). :- mode tag_switch__generate_secondary_try_me_else_chain(in, in, in, in, in, in, in, out, in, out) is det. tag_switch__generate_secondary_try_me_else_chain([], _, _, _, _, _, _, _) --> { error("generate_secondary_try_me_else_chain: empty switch") }. tag_switch__generate_secondary_try_me_else_chain([Case0 | Cases0], StagRval, CodeModel, CanFail, StoreMap, 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, StagRval, const(int_const(Secondary))), label(ElseLabel)) - "test remote sec tag only" ]) }, trace__maybe_generate_internal_event_code(Goal, TraceCode), code_gen__generate_goal(CodeModel, Goal, GoalCode), code_info__generate_branch_end(CodeModel, StoreMap, SaveCode), { GotoLabelCode = node([ goto(label(EndLabel)) - "skip to end of secondary tag switch", label(ElseLabel) - "handle next secondary tag" ]) }, { ThisCode = tree(TestCode, tree(TraceCode, tree(GoalCode, tree(SaveCode, GotoLabelCode)))) }, ( { Cases0 = [_|_] } -> code_info__slap_code_info(CodeInfo), tag_switch__generate_secondary_try_me_else_chain(Cases0, StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, OtherCode), { Code = tree(ThisCode, OtherCode) } ; { FailCode = node([ goto(label(FailLabel)) - "secondary tag does not match" ]) }, { Code = tree(ThisCode, FailCode) } ) ; trace__maybe_generate_internal_event_code(Goal, TraceCode), code_gen__generate_goal(CodeModel, Goal, GoalCode), code_info__generate_branch_end(CodeModel, StoreMap, SaveCode), { GotoCode = node([ goto(label(EndLabel)) - "skip to end of secondary tag switch" ]) }, { Code = tree(TraceCode, tree(GoalCode, tree(SaveCode, GotoCode))) } ). %-----------------------------------------------------------------------------% % Generate a switch on a secondary tag value using a try chain. :- pred tag_switch__generate_secondary_try_chain(stag_goal_list, rval, code_model, can_fail, store_map, label, label, code_tree, code_tree, code_tree, code_info, code_info). :- mode tag_switch__generate_secondary_try_chain(in, in, in, in, in, in, in, in, in, out, in, out) is det. tag_switch__generate_secondary_try_chain([], _, _, _, _, _, _, _, _, _) --> { error("generate_secondary_try_chain: empty switch") }. tag_switch__generate_secondary_try_chain([Case0 | Cases0], StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PrevTests0, PrevCases0, Code) --> { Case0 = Secondary - Goal }, ( { Cases0 = [_|_] ; CanFail = can_fail } -> code_info__grab_code_info(CodeInfo), code_info__get_next_label(ThisStagLabel), { TestCode = node([ if_val(binop(eq, StagRval, const(int_const(Secondary))), label(ThisStagLabel)) - "test remote sec tag only" ]) }, { LabelCode = node([ label(ThisStagLabel) - "handle next secondary tag" ]) }, trace__maybe_generate_internal_event_code(Goal, TraceCode), code_gen__generate_goal(CodeModel, Goal, GoalCode), code_info__generate_branch_end(CodeModel, StoreMap, SaveCode), { GotoCode = node([ goto(label(EndLabel)) - "skip to end of secondary tag switch" ]) }, { ThisCode = tree(LabelCode, tree(TraceCode, tree(GoalCode, tree(SaveCode, GotoCode)))) }, { PrevTests = tree(PrevTests0, TestCode) }, { PrevCases = tree(ThisCode, PrevCases0) }, ( { Cases0 = [_|_] } -> code_info__slap_code_info(CodeInfo), tag_switch__generate_secondary_try_chain(Cases0, StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, PrevTests, PrevCases, Code) ; { FailCode = node([ goto(label(FailLabel)) - "secondary tag with no code to handle it" ]) }, { Code = tree(PrevTests, tree(FailCode, PrevCases)) } ) ; trace__maybe_generate_internal_event_code(Goal, TraceCode), code_gen__generate_goal(CodeModel, Goal, GoalCode), code_info__generate_branch_end(CodeModel, StoreMap, SaveCode), { GotoCode = node([ goto(label(EndLabel)) - "skip to end of secondary tag switch" ]) }, { Code = tree(PrevTests0, tree(TraceCode, tree(GoalCode, tree(SaveCode, tree(GotoCode, PrevCases0))))) } ). %-----------------------------------------------------------------------------% % 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_jump_table(stag_goal_list, int, int, code_model, store_map, label, label, list(label), code_tree, code_info, code_info). :- mode tag_switch__generate_secondary_jump_table(in, in, in, in, in, in, in, out, out, in, out) is det. tag_switch__generate_secondary_jump_table(CaseList, CurSecondary, MaxSecondary, CodeModel, StoreMap, 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), { LabelCode = node([ label(NewLabel) - "start of case in secondary tag switch" ]) }, code_info__grab_code_info(CodeInfo), trace__maybe_generate_internal_event_code(Goal, TraceCode), code_gen__generate_goal(CodeModel, Goal, GoalCode), code_info__generate_branch_end(CodeModel, StoreMap, SaveCode), ( { CaseList1 = [] } -> [] ; code_info__slap_code_info(CodeInfo) ), { GotoCode = node([ goto(label(EndLabel)) - "branch to end of tag switch" ]) }, tag_switch__generate_secondary_jump_table(CaseList1, NextSecondary, MaxSecondary, CodeModel, StoreMap, EndLabel, FailLabel, OtherLabels, OtherCode), { Labels = [NewLabel | OtherLabels] }, { Code = tree(LabelCode, tree(TraceCode, tree(GoalCode, tree(SaveCode, tree(GotoCode, OtherCode))))) } ; tag_switch__generate_secondary_jump_table(CaseList, NextSecondary, MaxSecondary, CodeModel, StoreMap, EndLabel, FailLabel, OtherLabels, Code), { Labels = [FailLabel | OtherLabels] } ) ). %-----------------------------------------------------------------------------% % Generate the cases for a secondary tag using a binary search. % This invocation looks after secondary tag values in the range % MinPtag to MaxPtag (including both boundary values). :- pred tag_switch__generate_secondary_binary_search(stag_goal_list, int, int, rval, code_model, can_fail, store_map, label, label, maybe(code_info), maybe(code_info), code_tree, code_info, code_info). :- mode tag_switch__generate_secondary_binary_search(in, in, in, in, in, in, in, in, in, in, out, out, in, out) is det. tag_switch__generate_secondary_binary_search(StagGoals, MinStag, MaxStag, StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, MaybeFinalCodeInfo0, MaybeFinalCodeInfo, Code) --> ( { MinStag = MaxStag } -> { CurSec = MinStag }, ( { StagGoals = [] } -> % there is no code for this tag ( { CanFail = can_fail }, { string__int_to_string(CurSec, StagStr) }, { string__append("no code for ptag ", StagStr, Comment) }, { Code = node([ goto(label(FailLabel)) - Comment ]) } ; { CanFail = cannot_fail }, { Code = empty } ), { MaybeFinalCodeInfo = MaybeFinalCodeInfo0 } ; { StagGoals = [CurSec - Goal] } -> trace__maybe_generate_internal_event_code(Goal, TraceCode), code_gen__generate_goal(CodeModel, Goal, GoalCode), code_info__generate_branch_end(CodeModel, StoreMap, SaveCode), { Code = tree(TraceCode, tree(GoalCode, SaveCode)) }, code_info__grab_code_info(CodeInfo), { MaybeFinalCodeInfo = yes(CodeInfo) } ; { error("goallist not singleton or empty when binary search ends") } ) ; { LowRangeEnd is (MinStag + MaxStag) // 2 }, { HighRangeStart is LowRangeEnd + 1 }, { InLowGroup = lambda([StagGoal::in] is semidet, ( StagGoal = Stag - _, Stag =< LowRangeEnd )) }, { list__filter(InLowGroup, StagGoals, LowGoals, HighGoals) }, code_info__get_next_label(NewLabel), { string__int_to_string(MinStag, LowStartStr) }, { string__int_to_string(LowRangeEnd, LowEndStr) }, { string__int_to_string(HighRangeStart, HighStartStr) }, { string__int_to_string(MaxStag, HighEndStr) }, { string__append_list(["fallthrough for stags ", LowStartStr, " to ", LowEndStr], IfComment) }, { string__append_list(["code for stags ", HighStartStr, " to ", HighEndStr], LabelComment) }, { LowRangeEndConst = const(int_const(LowRangeEnd)) }, { TestRval = binop(>, StagRval, LowRangeEndConst) }, { IfCode = node([ if_val(TestRval, label(NewLabel)) - IfComment ]) }, { LabelCode = node([ label(NewLabel) - LabelComment ]) }, code_info__grab_code_info(CodeInfo), tag_switch__generate_secondary_binary_search(LowGoals, MinStag, LowRangeEnd, StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, MaybeFinalCodeInfo0, MaybeFinalCodeInfo1, LowRangeCode), code_info__slap_code_info(CodeInfo), tag_switch__generate_secondary_binary_search(HighGoals, HighRangeStart, MaxStag, StagRval, CodeModel, CanFail, StoreMap, EndLabel, FailLabel, MaybeFinalCodeInfo1, MaybeFinalCodeInfo, HighRangeCode), { Code = tree(IfCode, tree(LowRangeCode, tree(LabelCode, HighRangeCode))) } ). %-----------------------------------------------------------------------------% % Find out how many secondary tags share each primary tag % of the given variable. :- pred tag_switch__get_ptag_counts(type, module_info, int, ptag_count_map). :- mode tag_switch__get_ptag_counts(in, in, out, out) is det. tag_switch__get_ptag_counts(Type, ModuleInfo, MaxPrimary, PtagCountMap) :- ( type_to_type_id(Type, TypeIdPrime, _) -> TypeId = TypeIdPrime ; error("unknown type in tag_switch__get_ptag_counts") ), module_info_types(ModuleInfo, TypeTable), map__lookup(TypeTable, TypeId, TypeDefn), hlds_data__get_type_defn_body(TypeDefn, Body), ( Body = 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_ptag_counts") ), map__init(PtagCountMap0), tag_switch__get_ptag_counts_2(TagList, -1, MaxPrimary, PtagCountMap0, PtagCountMap). :- pred tag_switch__get_ptag_counts_2(list(cons_tag), int, int, ptag_count_map, ptag_count_map). :- mode tag_switch__get_ptag_counts_2(in, in, out, in, out) is det. tag_switch__get_ptag_counts_2([], Max, Max, PtagCountMap, PtagCountMap). tag_switch__get_ptag_counts_2([ConsTag | TagList], MaxPrimary0, MaxPrimary, PtagCountMap0, PtagCountMap) :- ( ConsTag = simple_tag(Primary) -> int__max(MaxPrimary0, Primary, MaxPrimary1), ( map__search(PtagCountMap0, Primary, _) -> error("simple tag is shared") ; map__det_insert(PtagCountMap0, Primary, none - (-1), PtagCountMap1) ) ; ConsTag = complicated_tag(Primary, Secondary) -> int__max(MaxPrimary0, Primary, MaxPrimary1), ( map__search(PtagCountMap0, Primary, Target) -> Target = TagType - MaxSoFar, ( TagType = remote -> true ; error("remote tag is shared with non-remote") ), int__max(Secondary, MaxSoFar, Max), map__det_update(PtagCountMap0, Primary, remote - Max, PtagCountMap1) ; map__det_insert(PtagCountMap0, Primary, remote - Secondary, PtagCountMap1) ) ; ConsTag = complicated_constant_tag(Primary, Secondary) -> int__max(MaxPrimary0, Primary, MaxPrimary1), ( map__search(PtagCountMap0, Primary, Target) -> Target = TagType - MaxSoFar, ( TagType = local -> true ; error("local tag is shared with non-local") ), int__max(Secondary, MaxSoFar, Max), map__det_update(PtagCountMap0, Primary, local - Max, PtagCountMap1) ; map__det_insert(PtagCountMap0, Primary, local - Secondary, PtagCountMap1) ) ; error("non-du tag in tag_switch__get_ptag_counts_2") ), tag_switch__get_ptag_counts_2(TagList, MaxPrimary1, MaxPrimary, PtagCountMap1, PtagCountMap). %-----------------------------------------------------------------------------% % Group together all the cases that depend on the given variable % having the same primary tag value. :- pred tag_switch__group_cases_by_ptag(cases_list, ptag_case_map, ptag_case_map). :- mode tag_switch__group_cases_by_ptag(in, in, out) is det. tag_switch__group_cases_by_ptag([], PtagCaseMap, PtagCaseMap). tag_switch__group_cases_by_ptag([Case0 | Cases0], PtagCaseMap0, PtagCaseMap) :- Case0 = case(_Priority, Tag, _ConsId, Goal), ( Tag = simple_tag(Primary) -> ( map__search(PtagCaseMap0, Primary, _Group) -> error("simple tag is shared") ; map__init(StagGoalMap0), map__det_insert(StagGoalMap0, -1, Goal, StagGoalMap), map__det_insert(PtagCaseMap0, Primary, none - StagGoalMap, PtagCaseMap1) ) ; Tag = complicated_tag(Primary, Secondary) -> ( map__search(PtagCaseMap0, Primary, Group) -> Group = StagLoc - StagGoalMap0, ( StagLoc = remote -> true ; error("remote tag is shared with non-remote") ), map__det_insert(StagGoalMap0, Secondary, Goal, StagGoalMap), map__det_update(PtagCaseMap0, Primary, remote - StagGoalMap, PtagCaseMap1) ; map__init(StagGoalMap0), map__det_insert(StagGoalMap0, Secondary, Goal, StagGoalMap), map__det_insert(PtagCaseMap0, Primary, remote - StagGoalMap, PtagCaseMap1) ) ; Tag = complicated_constant_tag(Primary, Secondary) -> ( map__search(PtagCaseMap0, Primary, Group) -> Group = StagLoc - StagGoalMap0, ( StagLoc = local -> true ; error("local tag is shared with non-local") ), map__det_insert(StagGoalMap0, Secondary, Goal, StagGoalMap), map__det_update(PtagCaseMap0, Primary, local - StagGoalMap, PtagCaseMap1) ; map__init(StagGoalMap0), map__det_insert(StagGoalMap0, Secondary, Goal, StagGoalMap), map__det_insert(PtagCaseMap0, Primary, local - StagGoalMap, PtagCaseMap1) ) ; error("non-du tag in tag_switch__group_cases_by_ptag") ), tag_switch__group_cases_by_ptag(Cases0, PtagCaseMap1, PtagCaseMap). %-----------------------------------------------------------------------------% % Order the primary tags based on the number of secondary tags % associated with them, putting the ones with the most secondary tags % first. We use selection sort. % Note that it is not an error for a primary tag to have no case list; % this can happen in semideterministic switches, or in det switches % where the initial inst of the switch variable is a bound(...) inst % representing a subtype. :- pred tag_switch__order_ptags_by_count(ptag_count_list, ptag_case_map, ptag_case_list). :- mode tag_switch__order_ptags_by_count(in, in, out) is det. tag_switch__order_ptags_by_count(PtagCountList0, PtagCaseMap0, PtagCaseList) :- ( tag_switch__select_frequent_ptag(PtagCountList0, Primary, _, PtagCountList1) -> ( map__search(PtagCaseMap0, Primary, PtagCase) -> map__delete(PtagCaseMap0, Primary, PtagCaseMap1), tag_switch__order_ptags_by_count(PtagCountList1, PtagCaseMap1, PtagCaseList1), PtagCaseList = [Primary - PtagCase | PtagCaseList1] ; tag_switch__order_ptags_by_count(PtagCountList1, PtagCaseMap0, PtagCaseList) ) ; ( map__is_empty(PtagCaseMap0) -> PtagCaseList = [] ; error("PtagCaseMap0 is not empty in tag_switch__order_ptags_by_count") ) ). % Select the most frequently used primary tag based on the number of % secondary tags associated with it. :- pred tag_switch__select_frequent_ptag(ptag_count_list, tag_bits, int, ptag_count_list). :- mode tag_switch__select_frequent_ptag(in, out, out, out) is semidet. tag_switch__select_frequent_ptag([PtagCount0 | PtagCountList1], Primary, Count, PtagCountList) :- PtagCount0 = Primary0 - (_ - Count0), ( tag_switch__select_frequent_ptag(PtagCountList1, Primary1, Count1, PtagCountList2), Count1 > Count0 -> Primary = Primary1, Count = Count1, PtagCountList = [PtagCount0 | PtagCountList2] ; Primary = Primary0, Count = Count0, PtagCountList = PtagCountList1 ). %-----------------------------------------------------------------------------% % Order the primary tags based on their value, lowest value first. % We scan through the primary tags values from zero to maximum. % 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_ptags_by_value(int, int, ptag_case_map, ptag_case_list). :- mode tag_switch__order_ptags_by_value(in, in, in, out) is det. tag_switch__order_ptags_by_value(Ptag, MaxPtag, PtagCaseMap0, PtagCaseList) :- ( MaxPtag >= Ptag -> NextPtag is Ptag + 1, ( map__search(PtagCaseMap0, Ptag, PtagCase) -> map__delete(PtagCaseMap0, Ptag, PtagCaseMap1), tag_switch__order_ptags_by_value(NextPtag, MaxPtag, PtagCaseMap1, PtagCaseList1), PtagCaseList = [Ptag - PtagCase | PtagCaseList1] ; tag_switch__order_ptags_by_value(NextPtag, MaxPtag, PtagCaseMap0, PtagCaseList) ) ; ( map__is_empty(PtagCaseMap0) -> PtagCaseList = [] ; error("PtagCaseMap0 is not empty in order_ptags_by_value") ) ). %-----------------------------------------------------------------------------% :- 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).