mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-19 15:54:18 +00:00
tag_switch: Fixed two bugs. First, if a primary tag value did not have cases for all its secondary tag values, we now emit a goto the failure label if the secondary tag does not match any case; we used to just fall through. Second, the failure code itself used to be generated in the context of the end of one of the cases; this should now be fixed, although I want to go over it with Tom to make sure. The computation of the secondary tag is now done once, instead of being repeated at every secondary tag test. options: Set tag_switch_size to 4 by default, reduced from 8. It was this change that exposed the two bugs above. After the fix, the compiler is smaller by about 2 Kb. switch_gen: Add some comments. code_util: Fixed nonstandard indentation.
297 lines
11 KiB
Mathematica
297 lines
11 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.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% File: switch_gen.m
|
|
% Authors: conway, fjh, zs
|
|
%
|
|
% This module handles the generation of code for switches, which are
|
|
% disjunctions that do not require backtracking. Switches are detected
|
|
% in switch_detection.m. This is the module that determines what
|
|
% sort of indexing to use for each switch and then actually generates the
|
|
% code.
|
|
%
|
|
% Currently the following forms of indexing are used:
|
|
%
|
|
% For switches on atomic data types (int, char, enums),
|
|
% if the cases are not sparse, we use the value of the switch variable
|
|
% to index into a jump table.
|
|
%
|
|
% For switches on discriminated union types, we generate a chain of
|
|
% if-then-elses on the primary tags but then use the secondary tag
|
|
% to index into a jump table if the table is big enough.
|
|
%
|
|
% For switches on strings, we lookup the address to jump to in a
|
|
% hash table, using open addressing to resolve hash collisions.
|
|
%
|
|
% For all other cases (or if the --smart-indexing option was
|
|
% disabled), we just generate a chain of if-then-elses.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module switch_gen.
|
|
|
|
:- interface.
|
|
|
|
:- import_module hlds, code_info.
|
|
|
|
:- pred switch_gen__generate_switch(code_model, var, can_fail, list(case),
|
|
code_tree, code_info, code_info).
|
|
:- mode switch_gen__generate_switch(in, in, in, in, out, in, out) is det.
|
|
|
|
% These types are exported to dense_switch, string_switch and tag_switch.
|
|
|
|
:- type extended_case ---> case(int, cons_tag, cons_id, hlds__goal).
|
|
:- type cases_list == list(extended_case).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module dense_switch, string_switch, tag_switch.
|
|
:- import_module int, string, list, map, tree, std_util, require.
|
|
:- import_module llds, code_gen, unify_gen, type_util, code_util.
|
|
:- import_module globals, options.
|
|
|
|
:- type switch_category
|
|
---> atomic_switch
|
|
; string_switch
|
|
; tag_switch
|
|
; other_switch.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Choose which method to use to generate the switch.
|
|
% CanFail says whether the switch covers all cases.
|
|
|
|
switch_gen__generate_switch(CodeModel, CaseVar, CanFail, Cases, Code) -->
|
|
switch_gen__determine_category(CaseVar, SwitchCategory),
|
|
code_info__get_next_label(EndLabel),
|
|
switch_gen__lookup_tags(Cases, CaseVar, TaggedCases0),
|
|
{ list__sort_and_remove_dups(TaggedCases0, TaggedCases) },
|
|
code_info__get_globals(Globals),
|
|
{ globals__lookup_bool_option(Globals, smart_indexing,
|
|
Indexing) },
|
|
(
|
|
{ Indexing = yes },
|
|
{ SwitchCategory = atomic_switch },
|
|
{ list__length(TaggedCases, NumCases) },
|
|
{ globals__lookup_int_option(Globals, dense_switch_size,
|
|
DenseSize) },
|
|
{ NumCases >= DenseSize },
|
|
{ globals__lookup_int_option(Globals, req_density,
|
|
ReqDensity) },
|
|
dense_switch__is_dense_switch(CaseVar, TaggedCases, CanFail,
|
|
ReqDensity, FirstVal, LastVal, CanFail1)
|
|
->
|
|
dense_switch__generate(TaggedCases,
|
|
FirstVal, LastVal, CaseVar, CodeModel, CanFail1,
|
|
EndLabel, Code)
|
|
;
|
|
{ Indexing = yes },
|
|
{ SwitchCategory = string_switch },
|
|
{ list__length(TaggedCases, NumCases) },
|
|
{ globals__lookup_int_option(Globals, string_switch_size,
|
|
StringSize) },
|
|
{ NumCases >= StringSize }
|
|
->
|
|
string_switch__generate(TaggedCases, CaseVar, CodeModel,
|
|
CanFail, EndLabel, Code)
|
|
;
|
|
{ Indexing = yes },
|
|
{ SwitchCategory = tag_switch },
|
|
{ list__length(TaggedCases, NumCases) },
|
|
{ globals__lookup_int_option(Globals, tag_switch_size,
|
|
TagSize) },
|
|
{ NumCases >= TagSize }
|
|
->
|
|
tag_switch__generate(TaggedCases,
|
|
CaseVar, CodeModel, CanFail, EndLabel, Code)
|
|
;
|
|
% To generate a switch, first we flush the
|
|
% variable on whose tag we are going to switch, then we
|
|
% generate the cases for the switch.
|
|
|
|
switch_gen__generate_all_cases(TaggedCases,
|
|
CaseVar, CodeModel, CanFail, EndLabel, Code)
|
|
),
|
|
code_info__remake_with_store_map.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% We categorize switches according to whether the value
|
|
% being switched on is an atomic type, a string, or
|
|
% something more complicated.
|
|
|
|
:- pred switch_gen__determine_category(var, switch_category,
|
|
code_info, code_info).
|
|
:- mode switch_gen__determine_category(in, out, in, out) is det.
|
|
|
|
switch_gen__determine_category(CaseVar, SwitchCategory) -->
|
|
code_info__variable_type(CaseVar, Type),
|
|
code_info__get_module_info(ModuleInfo),
|
|
{ classify_type(Type, ModuleInfo, TypeCategory) },
|
|
{ switch_gen__type_cat_to_switch_cat(TypeCategory, SwitchCategory) }.
|
|
|
|
:- pred switch_gen__type_cat_to_switch_cat(builtin_type, switch_category).
|
|
:- mode switch_gen__type_cat_to_switch_cat(in, out) is det.
|
|
|
|
switch_gen__type_cat_to_switch_cat(enum_type, atomic_switch).
|
|
switch_gen__type_cat_to_switch_cat(int_type, atomic_switch).
|
|
switch_gen__type_cat_to_switch_cat(char_type, atomic_switch).
|
|
switch_gen__type_cat_to_switch_cat(float_type, other_switch).
|
|
switch_gen__type_cat_to_switch_cat(str_type, string_switch).
|
|
switch_gen__type_cat_to_switch_cat(pred_type, other_switch).
|
|
switch_gen__type_cat_to_switch_cat(user_type(_), tag_switch).
|
|
switch_gen__type_cat_to_switch_cat(polymorphic_type, other_switch).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred switch_gen__lookup_tags(list(case), var, cases_list,
|
|
code_info, code_info).
|
|
:- mode switch_gen__lookup_tags(in, in, out, in, out) is det.
|
|
|
|
switch_gen__lookup_tags([], _, []) --> [].
|
|
switch_gen__lookup_tags([Case | Cases], Var, [TaggedCase | TaggedCases]) -->
|
|
{ Case = case(ConsId, Goal) },
|
|
code_info__cons_id_to_tag(Var, ConsId, Tag),
|
|
{ switch_gen__priority(Tag, Priority) },
|
|
{ TaggedCase = case(Priority, Tag, ConsId, Goal) },
|
|
switch_gen__lookup_tags(Cases, Var, TaggedCases).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred switch_gen__priority(cons_tag, int).
|
|
:- mode switch_gen__priority(in, out) is det.
|
|
|
|
% prioritize tag tests - the most efficient ones first.
|
|
|
|
switch_gen__priority(int_constant(_), 1).
|
|
switch_gen__priority(complicated_constant_tag(_, _), 1).
|
|
switch_gen__priority(simple_tag(_), 2).
|
|
switch_gen__priority(float_constant(_), 3).
|
|
switch_gen__priority(complicated_tag(_, _), 4).
|
|
switch_gen__priority(string_constant(_), 5).
|
|
switch_gen__priority(pred_closure_tag(_, _), 6).
|
|
switch_gen__priority(address_constant(_, _), 6).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Generate a switch as a chain of if-then-elses.
|
|
%
|
|
% To generate a case for a switch we generate
|
|
% code to do a tag-test and fall through to the next case in
|
|
% the event of failure.
|
|
%
|
|
% Each case except the last consists of a tag test, followed by
|
|
% the goal for that case, followed by a branch to the end of
|
|
% the switch. The goal is generated as a "forced" goal which
|
|
% ensures that all variables which are live at the end of the
|
|
% case get stored in their stack slot. For the last case, if
|
|
% the switch is locally deterministic, then we don't need to
|
|
% generate the tag test, and we never need to generate the
|
|
% branch to the end of the switch. After the last case, we put
|
|
% the end-of-switch label which other cases branch to after
|
|
% their case goals.
|
|
%
|
|
% In the important special case of a det switch with two cases,
|
|
% we put the code for the second case first, since it is much
|
|
% more likely to be the recursive and therefore frequently executed
|
|
% case, while using a negated form of the test for the first case,
|
|
% since this is cheaper.
|
|
|
|
:- pred switch_gen__generate_all_cases(list(extended_case), var,
|
|
code_model, can_fail, label, code_tree, code_info, code_info).
|
|
:- mode switch_gen__generate_all_cases(in, in, in, in, in, out, in, out) is det.
|
|
|
|
switch_gen__generate_all_cases(Cases, Var, CodeModel, CanFail, EndLabel, Code) -->
|
|
code_info__produce_variable(Var, VarCode, _Rval),
|
|
(
|
|
{ CodeModel = model_det },
|
|
{ CanFail = cannot_fail },
|
|
{ Cases = [Case1, Case2] }
|
|
->
|
|
{ Case1 = case(_, _, Cons1, Goal1) },
|
|
unify_gen__generate_tag_test(Var, Cons1, NextLab, TestCode),
|
|
code_info__grab_code_info(CodeInfo),
|
|
code_gen__generate_forced_goal(CodeModel, Goal1, Case1Code),
|
|
|
|
{ Case2 = case(_, _, _Cons2, Goal2) },
|
|
code_info__slap_code_info(CodeInfo),
|
|
code_gen__generate_forced_goal(CodeModel, Goal2, Case2Code),
|
|
|
|
{ tree__flatten(TestCode, TestListList) },
|
|
{ list__condense(TestListList, TestList) },
|
|
{ code_util__negate_the_test(TestList, RealTestList) },
|
|
{ Code = tree(
|
|
tree(
|
|
VarCode,
|
|
tree(
|
|
node(RealTestList),
|
|
Case2Code)),
|
|
tree(
|
|
node([
|
|
goto(label(EndLabel)) -
|
|
"skip to the end of the switch",
|
|
label(NextLab) - "next case" ]),
|
|
tree(
|
|
Case1Code,
|
|
node([ label(EndLabel) -
|
|
"End of switch" ]))))
|
|
}
|
|
;
|
|
switch_gen__generate_cases(Cases, Var, CodeModel, CanFail,
|
|
EndLabel, CasesCode),
|
|
{ Code = tree(VarCode, CasesCode) }
|
|
).
|
|
|
|
:- pred switch_gen__generate_cases(list(extended_case), var,
|
|
code_model, can_fail, label, code_tree, code_info, code_info).
|
|
:- mode switch_gen__generate_cases(in, in, in, in, in, out, in, out) is det.
|
|
|
|
% At the end of a locally semidet switch, we fail because we
|
|
% came across a tag which was not covered by one of the cases.
|
|
% It is followed by the end of switch label to which the cases
|
|
% branch.
|
|
switch_gen__generate_cases([], _Var, _CodeModel, CanFail, EndLabel, Code) -->
|
|
( { CanFail = can_fail } ->
|
|
code_info__generate_failure(FailCode)
|
|
;
|
|
{ FailCode = empty }
|
|
),
|
|
{ Code = tree(FailCode, node([ label(EndLabel) -
|
|
"End of switch" ])) }.
|
|
|
|
% A case consists of a tag-test followed by a
|
|
% goal and a label for the start of the next case.
|
|
switch_gen__generate_cases([case(_, _, Cons, Goal)|Cases], Var, CodeModel,
|
|
CanFail, EndLabel, CasesCode) -->
|
|
code_info__grab_code_info(CodeInfo),
|
|
(
|
|
{ Cases = [_|_] ; CanFail = can_fail }
|
|
->
|
|
unify_gen__generate_tag_test(Var, Cons, NextLab, TestCode),
|
|
code_gen__generate_forced_goal(CodeModel, Goal, ThisCode),
|
|
{ ElseCode = node([
|
|
goto(label(EndLabel)) - "skip to the end of the switch",
|
|
label(NextLab) - "next case"
|
|
]) },
|
|
{ ThisCaseCode = tree(tree(TestCode, ThisCode), ElseCode) },
|
|
code_info__grab_code_info(CodeInfo1),
|
|
code_info__slap_code_info(CodeInfo)
|
|
;
|
|
code_gen__generate_forced_goal(CodeModel, Goal, ThisCaseCode),
|
|
code_info__grab_code_info(CodeInfo1),
|
|
code_info__slap_code_info(CodeInfo)
|
|
),
|
|
% generate the rest of the cases.
|
|
switch_gen__generate_cases(Cases, Var, CodeModel, CanFail, EndLabel,
|
|
CasesCode0),
|
|
{ CasesCode = tree(ThisCaseCode, CasesCode0) },
|
|
code_info__slap_code_info(CodeInfo1).
|
|
|
|
%------------------------------------------------------------------------------%
|