mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-16 06:14:59 +00:00
Estimated hours taken: 18 Branches: main Move the univ, maybe, pair and unit types from std_util into their own modules. std_util still contains the general purpose higher-order programming constructs. library/std_util.m: Move univ, maybe, pair and unit (plus any other related types and procedures) into their own modules. library/maybe.m: New module. This contains the maybe and maybe_error types and the associated procedures. library/pair.m: New module. This contains the pair type and associated procedures. library/unit.m: New module. This contains the types unit/0 and unit/1. library/univ.m: New module. This contains the univ type and associated procedures. library/library.m: Add the new modules. library/private_builtin.m: Update the declaration of the type_ctor_info struct for univ. runtime/mercury.h: Update the declaration for the type_ctor_info struct for univ. runtime/mercury_mcpp.h: runtime/mercury_hlc_types.h: Update the definition of MR_Univ. runtime/mercury_init.h: Fix a comment: ML_type_name is now exported from type_desc.m. compiler/mlds_to_il.m: Update the the name of the module that defines univs (which are handled specially by the il code generator.) library/*.m: compiler/*.m: browser/*.m: mdbcomp/*.m: profiler/*.m: deep_profiler/*.m: Conform to the above changes. Import the new modules where they are needed; don't import std_util where it isn't needed. Fix formatting in lots of modules. Delete duplicate module imports. tests/*: Update the test suite to confrom to the above changes.
462 lines
18 KiB
Mathematica
462 lines
18 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: ml_switch_gen.m
|
|
% Author: fjh (adapted from switch_gen.m)
|
|
%
|
|
% This module handles the generation of code for switches for the MLDS
|
|
% back-end. Switches are disjunctions that do not require backtracking.
|
|
% They 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. The code here is quite similar to the code in switch_gen.m, which
|
|
% does the same thing for the LLDS back-end.
|
|
%
|
|
% The following describes the different forms of indexing that we could use.
|
|
% Note that currently most of these are not implemented!
|
|
% The ones that are not are marked NYI (for "not yet implemented").
|
|
%
|
|
% 1 For switches on atomic data types (int, char, enums), there are
|
|
% several possibilities.
|
|
%
|
|
% a) If all the alternative goals for a switch on an atomic data type
|
|
% contain only construction unifications of constants, then we
|
|
% should generate a dense lookup table (an array) for each output
|
|
% variable of the switch, rather than computed goto, so that
|
|
% executing the switch becomes a matter of doing an array index for
|
|
% each output variable. (NYI)
|
|
% b) If the cases are not sparse, and the target supports computed
|
|
% gotos, we should use a computed_goto, unless the target supports
|
|
% switch statements and the `--prefer-switch' option is set. (NYI)
|
|
% c) If the target supports switch statements,
|
|
% we generate an MLDS switch statement.
|
|
%
|
|
% 2 For switches on strings, there are several possibilities.
|
|
%
|
|
% a) If the target supports indirect gotos, we should look up the address
|
|
% to jump to in a hash table (e.g. using open addressing to resolve
|
|
% hash collisions), and then jump to it using an indirect goto,
|
|
% unless the target supports string switch statements and the
|
|
% `--prefer-switch' option is set. (NYI)
|
|
% b) If the target supports string switches,
|
|
% we generate an MLDS switch statement.
|
|
%
|
|
% 3 For switches on discriminated union types, we generate code that does
|
|
% indexing first on the primary tag, and then on the secondary tag (if
|
|
% the primary tag is shared between several function symbols). The
|
|
% indexing code for switches on both primary and secondary tags can be
|
|
% in the form of a try-me-else chain, a try chain, a dense jump table
|
|
% or a binary search. (NYI)
|
|
%
|
|
% For all other cases (or if the --smart-indexing option was disabled),
|
|
% we just generate a chain of if-then-elses.
|
|
%
|
|
% TODO:
|
|
% - implement the things marked NYI above
|
|
% - optimize switches so that the recursive case comes first
|
|
% (see switch_gen.m).
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module ml_backend.ml_switch_gen.
|
|
:- interface.
|
|
|
|
:- import_module hlds.code_model.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module libs.globals.
|
|
:- import_module ml_backend.ml_code_util.
|
|
:- import_module ml_backend.mlds.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module list.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate MLDS code for a switch.
|
|
%
|
|
:- pred ml_gen_switch(prog_var::in, can_fail::in, list(case)::in,
|
|
code_model::in, prog_context::in,
|
|
mlds_defns::out, statements::out,
|
|
ml_gen_info::in, ml_gen_info::out) is det.
|
|
|
|
% Generate an appropriate default for a switch.
|
|
%
|
|
:- pred ml_switch_generate_default(can_fail::in, code_model::in,
|
|
prog_context::in, mlds_switch_default::out,
|
|
ml_gen_info::in, ml_gen_info::out) is det.
|
|
|
|
% Succeed iff the target supports the specified construct.
|
|
%
|
|
:- pred target_supports_int_switch(globals::in) is semidet.
|
|
:- pred target_supports_string_switch(globals::in) is semidet.
|
|
:- pred target_supports_goto(globals::in) is semidet.
|
|
:- pred target_supports_computed_goto(globals::in) is semidet.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module backend_libs.foreign.
|
|
:- import_module backend_libs.switch_util.
|
|
:- import_module check_hlds.type_util.
|
|
:- import_module hlds.hlds_data.
|
|
:- import_module libs.compiler_util.
|
|
:- import_module libs.options.
|
|
:- import_module libs.tree.
|
|
:- import_module ml_backend.ml_code_gen.
|
|
:- import_module ml_backend.ml_code_util.
|
|
:- import_module ml_backend.ml_simplify_switch.
|
|
:- import_module ml_backend.ml_string_switch.
|
|
:- import_module ml_backend.ml_tag_switch.
|
|
:- import_module ml_backend.ml_unify_gen.
|
|
|
|
:- import_module bool.
|
|
:- import_module int.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module string.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
ml_gen_switch(CaseVar, CanFail, Cases, CodeModel, Context, Decls, Statements,
|
|
!Info) :-
|
|
% Lookup the representation of the constructors for the tag tests
|
|
% and their corresponding priorities.
|
|
ml_switch_lookup_tags(!.Info, Cases, CaseVar, TaggedCases0),
|
|
|
|
% Sort the cases according to the priority of their tag tests.
|
|
list.sort_and_remove_dups(TaggedCases0, TaggedCases),
|
|
|
|
% Figure out what kind of switch this is.
|
|
SwitchCategory = determine_category(!.Info, CaseVar),
|
|
ml_gen_info_get_globals(!.Info, Globals),
|
|
globals.lookup_bool_option(Globals, smart_indexing, Indexing),
|
|
(
|
|
% Check for a switch on a type whose representation
|
|
% uses reserved addresses.
|
|
list.member(Case, TaggedCases),
|
|
Case = case(_Priority, Tag, _ConsId, _Goal),
|
|
(
|
|
Tag = reserved_address(_)
|
|
;
|
|
Tag = shared_with_reserved_addresses(_, _)
|
|
)
|
|
->
|
|
% XXX This may be inefficient in some cases.
|
|
ml_switch_generate_if_else_chain(TaggedCases, CaseVar, CodeModel,
|
|
CanFail, Context, Decls, Statements, !Info)
|
|
;
|
|
% XXX Lookup switches are NYI
|
|
% When we do get around to implementing them,
|
|
% they should probably be handled in ml_simplify_switch rather than here.
|
|
% Indexing = yes,
|
|
% SwitchCategory = atomic_switch,
|
|
% % Note that if/when the MLDS back-end supports execution
|
|
% % tracing, we would also need to check that tracing is not
|
|
% % enabled.
|
|
% list.length(TaggedCases, NumCases),
|
|
% globals.lookup_int_option(Globals, lookup_switch_size,
|
|
% LookupSize),
|
|
% NumCases >= LookupSize,
|
|
% globals.lookup_int_option(Globals, lookup_switch_req_density,
|
|
% ReqDensity),
|
|
% lookup_switch.is_lookup_switch(CaseVar, TaggedCases, GoalInfo,
|
|
% CanFail, ReqDensity,
|
|
% CodeModel, FirstVal, LastVal, NeedRangeCheck,
|
|
% NeedBitVecCheck, OutVars, CaseVals, !Info)
|
|
% ->
|
|
% MaybeEnd = MaybeEndPrime,
|
|
% ml_lookup_switch.generate(CaseVar, OutVars, CaseVals,
|
|
% FirstVal, LastVal, NeedRangeCheck, NeedBitVecCheck,
|
|
% Decls, Statements, !Info)
|
|
% ;
|
|
% Try using a string hash switch.
|
|
Indexing = yes,
|
|
SwitchCategory = string_switch,
|
|
list.length(TaggedCases, NumCases),
|
|
globals.lookup_int_option(Globals, string_switch_size, StringSize),
|
|
NumCases >= StringSize,
|
|
% We can implement string hash switches using either
|
|
% computed gotos or int switches.
|
|
(
|
|
target_supports_computed_goto(Globals)
|
|
;
|
|
target_supports_int_switch(Globals)
|
|
),
|
|
% XXX Currently string hash switches always use gotos
|
|
% (to break out of the hash chain loop).
|
|
% We should change that, so that we can use string hash
|
|
% switches for the Java back-end too.
|
|
target_supports_goto(Globals),
|
|
% OK, we could use a string hash switch. But should we?
|
|
% We may prefer to do a direct-mapped string switch.
|
|
\+ (
|
|
target_supports_string_switch(Globals),
|
|
globals.lookup_bool_option(Globals, prefer_switch, yes)
|
|
)
|
|
->
|
|
ml_string_switch.generate(TaggedCases, CaseVar, CodeModel,
|
|
CanFail, Context, Decls, Statements, !Info)
|
|
;
|
|
% Try using a tag switch.
|
|
Indexing = yes,
|
|
SwitchCategory = tag_switch,
|
|
list.length(TaggedCases, NumCases),
|
|
globals.lookup_int_option(Globals, tag_switch_size, TagSize),
|
|
NumCases >= TagSize,
|
|
target_supports_int_switch(Globals)
|
|
->
|
|
ml_tag_switch.generate(TaggedCases, CaseVar, CodeModel,
|
|
CanFail, Context, Decls, Statements, !Info)
|
|
;
|
|
% Try using a "direct-mapped" switch. This also handles dense
|
|
% (computed goto) switches -- for those, we first generate a
|
|
% direct-mapped switch, and then convert it into a computed goto switch
|
|
% in ml_simplify_switch.
|
|
%
|
|
Indexing = yes,
|
|
(
|
|
target_supports_switch(SwitchCategory, Globals)
|
|
;
|
|
SwitchCategory = atomic_switch,
|
|
target_supports_computed_goto(Globals)
|
|
)
|
|
->
|
|
ml_switch_generate_mlds_switch(TaggedCases, CaseVar, CodeModel,
|
|
CanFail, Context, Decls, Statements, !Info)
|
|
;
|
|
% The fallback method: if all else fails, generate an if-then-else
|
|
% chain which tests each of the cases in turn.
|
|
ml_switch_generate_if_else_chain(TaggedCases, CaseVar, CodeModel,
|
|
CanFail, Context, Decls, Statements, !Info)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred target_supports_switch(switch_category::in, globals::in) is semidet.
|
|
|
|
target_supports_switch(SwitchCategory, Globals) :-
|
|
(
|
|
SwitchCategory = atomic_switch,
|
|
target_supports_int_switch(Globals)
|
|
;
|
|
SwitchCategory = string_switch,
|
|
target_supports_string_switch(Globals)
|
|
).
|
|
|
|
target_supports_int_switch(Globals) :-
|
|
globals.get_target(Globals, Target),
|
|
target_supports_int_switch_2(Target) = yes.
|
|
|
|
target_supports_string_switch(Globals) :-
|
|
globals.get_target(Globals, Target),
|
|
target_supports_string_switch_2(Target) = yes.
|
|
|
|
target_supports_goto(Globals) :-
|
|
globals.get_target(Globals, Target),
|
|
target_supports_goto_2(Target) = yes.
|
|
|
|
target_supports_computed_goto(Globals) :-
|
|
globals.get_target(Globals, Target),
|
|
target_supports_computed_goto_2(Target) = yes.
|
|
|
|
:- func target_supports_int_switch_2(compilation_target) = bool.
|
|
:- func target_supports_string_switch_2(compilation_target) = bool.
|
|
:- func target_supports_goto_2(compilation_target) = bool.
|
|
:- func target_supports_computed_goto_2(compilation_target) = bool.
|
|
|
|
target_supports_int_switch_2(c) = yes.
|
|
target_supports_int_switch_2(asm) = yes. % asm means via gnu back-end
|
|
target_supports_int_switch_2(il) = no.
|
|
target_supports_int_switch_2(java) = yes.
|
|
% target_supports_int_switch_2(c_sharp) = yes.
|
|
|
|
target_supports_string_switch_2(c) = no.
|
|
target_supports_string_switch_2(asm) = no. % asm means via gnu back-end
|
|
target_supports_string_switch_2(il) = no.
|
|
target_supports_string_switch_2(java) = no.
|
|
% target_supports_string_switch_2(c_sharp) = yes.
|
|
|
|
target_supports_computed_goto_2(c) = yes.
|
|
target_supports_computed_goto_2(asm) = no. % asm means via gnu back-end
|
|
% XXX for asm, it should be `yes', but currently
|
|
% computed gotos are not yet implemented in gcc.m.
|
|
target_supports_computed_goto_2(il) = yes.
|
|
target_supports_computed_goto_2(java) = no.
|
|
% target_supports_computed_goto_2(c_sharp) = no.
|
|
|
|
target_supports_goto_2(c) = yes.
|
|
target_supports_goto_2(asm) = yes. % asm means via gnu back-end
|
|
target_supports_goto_2(il) = yes.
|
|
target_supports_goto_2(java) = no.
|
|
% target_supports_goto_2(c_sharp) = yes.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% We categorize switches according to whether the value being switched on
|
|
% is an atomic type, a string, or something more complicated.
|
|
%
|
|
:- func determine_category(ml_gen_info, prog_var) = switch_category.
|
|
|
|
determine_category(Info, CaseVar) = SwitchCategory :-
|
|
ml_variable_type(Info, CaseVar, Type),
|
|
ml_gen_info_get_module_info(Info, ModuleInfo),
|
|
type_util.classify_type(ModuleInfo, Type) = TypeCategory,
|
|
SwitchCategory = switch_util.type_cat_to_switch_cat(TypeCategory).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Look up the representation (tag) for the cons_id in each case.
|
|
% Also look up the priority of each tag test.
|
|
%
|
|
:- pred ml_switch_lookup_tags(ml_gen_info::in, list(case)::in, prog_var::in,
|
|
cases_list::out) is det.
|
|
|
|
ml_switch_lookup_tags(_Info, [], _, []).
|
|
ml_switch_lookup_tags(Info, [Case | Cases], Var, [TaggedCase | TaggedCases]) :-
|
|
Case = case(ConsId, Goal),
|
|
ml_variable_type(Info, Var, Type),
|
|
ml_cons_id_to_tag(Info, ConsId, Type, Tag),
|
|
Priority = switch_util.switch_priority(Tag),
|
|
TaggedCase = case(Priority, Tag, ConsId, Goal),
|
|
ml_switch_lookup_tags(Info, Cases, Var, TaggedCases).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate a chain of if-then-elses to test each case in turn.
|
|
%
|
|
:- pred ml_switch_generate_if_else_chain(list(extended_case)::in, prog_var::in,
|
|
code_model::in, can_fail::in, prog_context::in,
|
|
mlds_defns::out, statements::out,
|
|
ml_gen_info::in, ml_gen_info::out) is det.
|
|
|
|
ml_switch_generate_if_else_chain([], _Var, CodeModel, CanFail, Context,
|
|
[], Statements, !Info) :-
|
|
(
|
|
CanFail = can_fail,
|
|
ml_gen_failure(CodeModel, Context, Statements, !Info)
|
|
;
|
|
CanFail = cannot_fail,
|
|
unexpected(this_file, "switch failure")
|
|
).
|
|
ml_switch_generate_if_else_chain([Case | Cases], Var, CodeModel, CanFail,
|
|
Context, Decls, Statements, !Info) :-
|
|
Case = case(_, _Tag, ConsId, Goal),
|
|
(
|
|
Cases = [],
|
|
CanFail = cannot_fail
|
|
->
|
|
ml_gen_goal(CodeModel, Goal, Decls, Statements, !Info)
|
|
;
|
|
ml_gen_tag_test(Var, ConsId, TagTestDecls, TagTestStatements,
|
|
TagTestExpression, !Info),
|
|
ml_gen_goal(CodeModel, Goal, GoalStatement, !Info),
|
|
ml_switch_generate_if_else_chain(Cases, Var, CodeModel,
|
|
CanFail, Context, RestDecls, RestStatements, !Info),
|
|
Rest = ml_gen_block(RestDecls, RestStatements, Context),
|
|
IfStmt = if_then_else(TagTestExpression, GoalStatement, yes(Rest)),
|
|
IfStatement = statement(IfStmt, mlds_make_context(Context)),
|
|
Decls = TagTestDecls,
|
|
Statements = TagTestStatements ++ [IfStatement]
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate an MLDS switch. This is used for "direct-mapped" switches,
|
|
% where we map a Mercury switch directly to a switch in the target
|
|
% language.
|
|
%
|
|
:- pred ml_switch_generate_mlds_switch(list(extended_case)::in, prog_var::in,
|
|
code_model::in, can_fail::in, prog_context::in, mlds_defns::out,
|
|
statements::out, ml_gen_info::in, ml_gen_info::out) is det.
|
|
|
|
ml_switch_generate_mlds_switch(Cases, Var, CodeModel, CanFail, Context,
|
|
Decls, Statements, !Info) :-
|
|
ml_variable_type(!.Info, Var, Type),
|
|
ml_gen_type(!.Info, Type, MLDS_Type),
|
|
ml_gen_var(!.Info, Var, Lval),
|
|
Rval = lval(Lval),
|
|
ml_switch_gen_range(!.Info, MLDS_Type, Range),
|
|
ml_switch_generate_mlds_cases(Cases, CodeModel, MLDS_Cases, !Info),
|
|
ml_switch_generate_default(CanFail, CodeModel, Context, Default, !Info),
|
|
SwitchStmt0 = switch(MLDS_Type, Rval, Range, MLDS_Cases, Default),
|
|
MLDS_Context = mlds_make_context(Context),
|
|
ml_simplify_switch(SwitchStmt0, MLDS_Context, SwitchStatement, !Info),
|
|
Decls = [],
|
|
Statements = [SwitchStatement].
|
|
|
|
:- pred ml_switch_gen_range(ml_gen_info::in, mlds_type::in,
|
|
mlds_switch_range::out) is det.
|
|
|
|
ml_switch_gen_range(Info, MLDS_Type, Range) :-
|
|
(
|
|
ml_gen_info_get_module_info(Info, ModuleInfo),
|
|
ExportedType = to_exported_type(ModuleInfo, Type),
|
|
MLDS_Type = mercury_type(Type, TypeCategory, ExportedType),
|
|
switch_util.type_range(TypeCategory, Type, ModuleInfo,
|
|
MinRange, MaxRange)
|
|
->
|
|
Range = range(MinRange, MaxRange)
|
|
;
|
|
Range = range_unknown
|
|
).
|
|
|
|
:- pred ml_switch_generate_mlds_cases(list(extended_case)::in,
|
|
code_model::in, list(mlds_switch_case)::out,
|
|
ml_gen_info::in, ml_gen_info::out) is det.
|
|
|
|
ml_switch_generate_mlds_cases([], _, [], !Info).
|
|
ml_switch_generate_mlds_cases([Case | Cases], CodeModel,
|
|
[MLDS_Case | MLDS_Cases], !Info) :-
|
|
ml_switch_generate_mlds_case(Case, CodeModel, MLDS_Case, !Info),
|
|
ml_switch_generate_mlds_cases(Cases, CodeModel, MLDS_Cases, !Info).
|
|
|
|
:- pred ml_switch_generate_mlds_case(extended_case::in, code_model::in,
|
|
mlds_switch_case::out,
|
|
ml_gen_info::in, ml_gen_info::out) is det.
|
|
|
|
ml_switch_generate_mlds_case(Case, CodeModel, MLDS_Case, !Info) :-
|
|
Case = case(_Priority, Tag, _ConsId, Goal),
|
|
( Tag = int_constant(Int) ->
|
|
Rval = const(int_const(Int))
|
|
; Tag = string_constant(String) ->
|
|
Rval = const(string_const(String))
|
|
;
|
|
unexpected(this_file, "ml_switch_gen.m: invalid tag type")
|
|
),
|
|
ml_gen_goal(CodeModel, Goal, Statement, !Info),
|
|
MLDS_Case = [match_value(Rval)] - Statement.
|
|
|
|
% Generate an appropriate default for a switch.
|
|
%
|
|
ml_switch_generate_default(CanFail, CodeModel, Context, Default, !Info) :-
|
|
(
|
|
CanFail = can_fail,
|
|
ml_gen_failure(CodeModel, Context, FailStatements, !Info),
|
|
(
|
|
FailStatements = [],
|
|
Default = default_do_nothing
|
|
;
|
|
FailStatements = [_ | _],
|
|
Fail = ml_gen_block([], FailStatements, Context),
|
|
Default = default_case(Fail)
|
|
)
|
|
;
|
|
CanFail = cannot_fail,
|
|
Default = default_is_unreachable
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func this_file = string.
|
|
|
|
this_file = "ml_switch_gen.m".
|
|
|
|
%-----------------------------------------------------------------------------%
|