Files
mercury/compiler/switch_gen.m
Zoltan Somogyi 707ed33da0 Fixed two bugs. First, if a primary tag value did not have cases for
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.
1995-09-24 08:45:34 +00:00

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).
%------------------------------------------------------------------------------%