%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2016 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% This module defines the types that represent information related to pragmas
% in the parse tree.
%
%---------------------------------------------------------------------------%

:- module parse_tree.prog_data_pragma.
:- interface.

:- import_module libs.
:- import_module libs.compiler_util.
:- import_module libs.rat.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.set_of_var.

:- import_module bool.
:- import_module list.
:- import_module maybe.
:- import_module one_or_more.
:- import_module pair.
:- import_module set.
:- import_module unit.

:- implementation.

:- import_module require.

%---------------------------------------------------------------------------%
%
% Stuff for tabling pragmas.
%

:- interface.

    % The evaluation method that should be used for a procedure.
    %
:- type eval_method
    --->    eval_normal                         % normal mercury evaluation
    ;       eval_tabled(tabled_eval_method).    % tabled evaluation

    % The evaluation method that should be used for a procedure.
    %
:- type tabled_eval_method
    --->    tabled_loop_check
            % loop check only
    ;       tabled_memo(
                % memoing + loop check
                % Preserve the value of this attribute until the invocation
                % of the relevant code in table_gen.m.
                table_attr_backend_warning
            )
    ;       tabled_io(
                % memoing I/O actions for debugging
                table_io_entry_kind,
                table_io_is_unitize
            )
    ;       tabled_minimal(
                % minimal model evaluation
                eval_minimal_method
            ).

:- type eval_minimal_method
    --->    stack_copy
            % Each minimal model procedure saves and restores stack segments
            % as necessary. See the paper "Tabling in Mercury" by Zoltan
            % Somogyi and Konstantinos Sagonas.

    ;       own_stacks_consumer
    ;       own_stacks_generator.
            % Each minimal model procedure is split into two: the consumer
            % and the generator. Each generator runs in its own context,
            % and thus has its own stacks.

:- type table_attributes
    --->    table_attributes(
                table_attr_strictness       :: call_table_strictness,
                table_attr_size_limit       :: maybe(int),
                table_attr_statistics       :: table_attr_statistics,
                table_attr_allow_reset      :: table_attr_allow_reset,
                table_attr_backend_warning  :: table_attr_backend_warning
            ).

:- func default_memo_table_attributes = table_attributes.

:- type table_attr_statistics
    --->    table_do_not_gather_statistics
    ;       table_gather_statistics.

:- type table_attr_allow_reset
    --->    table_do_not_allow_reset
    ;       table_allow_reset.

    % If the current backend cannot implement the requested form of tabling,
    % and is therefore forced to ignore it, should the compiler generate
    % a warning?
:- type table_attr_backend_warning
    --->    table_attr_ignore_with_warning      % Yes, generate a warning.
    ;       table_attr_ignore_without_warning.  % Do not generate a warning.

:- type call_table_strictness
    --->    cts_all_strict
    ;       cts_all_fast_loose
    ;       cts_specified(
                list(maybe(arg_tabling_method)),
                % This list contains one element for each user-visible
                % argument of the predicate. Elements that correspond
                % to output arguments should be "no". Elements that
                % correspond to input arguments should be "yes",
                % specifying how to look up that argument in the call table.

                hidden_arg_tabling_method
                % This specifies the tabling method for hidden arguments
                % introduced by the compiler.
            ).

:- type arg_tabling_method
    --->    arg_value
    ;       arg_addr
    ;       arg_promise_implied.

:- type hidden_arg_tabling_method
    --->    table_hidden_arg_value
    ;       table_hidden_arg_addr.

:- type table_io_entry_kind
    --->    entry_stores_outputs
            % Each entry in the I/O table stores only the outputs of the
            % action. The I/O action will be idempotent across retries
            % in mdb, but attempts to print out the action will cause
            % a core dump. This option is intended only for implementors
            % measuring the overheads of the two alternatives just below.

    ;       entry_stores_procid_outputs
            % Each entry in the I/O table starts with a pointer to the
            % MR_TableIoEntry structure of the procedure that performed
            % the action, and also contains the outputs of the action.
            % This makes the I/O action idempotent across retries and
            % allows the *name* of the I/O predicate to be printed
            % by mdb's "print action N" command, but not the values
            % of the arguments. Not even the output arguments can be printed,
            % since doing so requires knowing their types, and in general
            % that requires access to input type_info arguments.

    ;       entry_stores_procid_inputs_outputs.
            % Each entry in the I/O table starts with a pointer to the
            % MR_TableIoEntry structure of the procedure that performed
            % the action, and also contains both the inputs and outputs
            % of the action.
            %
            % This makes the I/O action idempotent across retries and
            % allows both the name and all the arguments of the I/O predicate
            % to be printed by mdb's "print action N" command. It also
            % allows the declarative debugger to consider the action to
            % be part of the effect of a call to its ancestors.

:- type table_io_is_unitize
    --->    table_io_unitize    % The procedure is tabled for I/O
                                % together with its Mercury descendants.

    ;       table_io_alone.     % The procedure is tabled for I/O by itself;
                                % it can have no Mercury descendants.

:- func tabled_eval_method_to_table_type(tabled_eval_method) = string.

:- implementation.

default_memo_table_attributes =
    table_attributes(cts_all_strict, no, table_do_not_gather_statistics,
        table_do_not_allow_reset, table_attr_ignore_with_warning).

tabled_eval_method_to_table_type(EvalMethod) = TableTypeStr :-
    (
        EvalMethod = tabled_io(_, _),
        unexpected($pred, "eval_table_io")
    ;
        EvalMethod = tabled_loop_check,
        TableTypeStr = "MR_TABLE_TYPE_LOOPCHECK"
    ;
        EvalMethod = tabled_memo(_),
        TableTypeStr = "MR_TABLE_TYPE_MEMO"
    ;
        EvalMethod = tabled_minimal(stack_copy),
        TableTypeStr = "MR_TABLE_TYPE_MINIMAL_MODEL_STACK_COPY"
    ;
        EvalMethod = tabled_minimal(own_stacks_consumer),
        unexpected($pred, "own_stacks_consumer")
    ;
        EvalMethod = tabled_minimal(own_stacks_generator),
        TableTypeStr = "MR_TABLE_TYPE_MINIMAL_MODEL_OWN_STACKS"
    ).

%---------------------------------------------------------------------------%
%
% Stuff for the `termination_info' pragma.
% See term_util.m.
%

:- interface.

:- type generic_arg_size_info(ErrorInfo)
    --->    finite(int, list(bool))
            % The termination constant is a finite integer. The list of bool
            % has a 1:1 correspondence with the input arguments of the
            % procedure. It stores whether the argument contributes to the
            % size of the output arguments.

    ;       infinite(ErrorInfo).
            % There is no finite integer for which the above equation is true.

:- type generic_termination_info(TermInfo, ErrorInfo)
    --->    cannot_loop(TermInfo)
            % This procedure definitely terminates for all possible inputs.
    ;       can_loop(ErrorInfo).
            % This procedure might not terminate.

:- type pragma_arg_size_info    == generic_arg_size_info(unit).
:- type pragma_termination_info == generic_termination_info(unit, unit).

%---------------------------------------------------------------------------%
%
% Stuff for the `termination2_info' pragma.
%

:- interface.

    % This is the form in which termination information from other
    % modules (imported via `.opt' or `.trans_opt' files) comes.
    % We convert this to an intermediate form and let the termination
    % analyser convert it to the correct form.
    %
    % NOTE: the reason that we cannot convert it to the correct form
    % is that we don't have complete information about how many typeinfo
    % related arguments there are until after the polymorphism pass.
    %
:- type arg_size_constr
    --->    le(list(arg_size_term), rat)
    ;       eq(list(arg_size_term), rat).

:- type arg_size_term
    --->    arg_size_term(
                as_term_var   :: int,
                as_term_coeff :: rat
            ).

:- type pragma_constr_arg_size_info == list(arg_size_constr).

%---------------------------------------------------------------------------%
%
% Stuff for the `structure_sharing_info' pragma.
%

:- interface.

    % Whenever structure sharing analysis is unable to determine a good
    % approximation of the set of structure sharing pairs that might exist
    % during the execution of a program, it must use "top" as the only safe
    % approximation.
    %
    % We divide the reasons for approximating by `top' into two cases:
    %
    % - the procedure calls some imported procedure for which we don't have an
    %   answer (yet). The result might be improved if we did have that
    %   information.
    %
    % - the procedure calls some imported procedure for which we managed to
    %   look up the answer, and that answer was `top'.
    %
    % - the procedure contains a call to foreign or generic code.
    %   Reanalysis will not improve the result.
    %
:- type top_feedback
    --->    top_failed_lookup(shrouded_pred_proc_id)
    ;       top_from_lookup(shrouded_pred_proc_id)
    ;       top_cannot_improve(string).

    % Elements of the structure sharing domain lattice are either bottom
    % (no structure sharing), top (any kind of structure sharing), or
    % a list of structure sharing pairs.
    %
    % This is the public representation of the type "sharing_as".
    %
:- type structure_sharing_domain
    --->    structure_sharing_bottom
    ;       structure_sharing_real(structure_sharing)
    ;       structure_sharing_top(set(top_feedback)).

    % Public representation of structure sharing.
    %
:- type structure_sharing == list(structure_sharing_pair).

    % A structure sharing pair represents the information that two
    % data structures might be represented by the same memoryspace, hence
    % its representation as a pair of datastructs.
    %
:- type structure_sharing_pair == pair(datastruct).

    % A datastruct is a concept that designates a particular subterm of the
    % term to which a particular variable may be bound. The selector is
    % normalized.
    %
:- type datastruct
    --->    selected_cel(
                sc_var      :: prog_var,
                sc_selector :: selector
            ).

    % A selector describes a path in a type-tree.
    %
:- type selector == list(unit_selector).

    % Unit-selectors are either term selectors or type selectors.
    % - A term selector selects a subterm f/n of a term, where f is a functor
    %   (identified by the cons_id), and n an integer.
    % - A type selector designates any subterm that has that specific type.
    %
:- type unit_selector
    --->    termsel(cons_id, int)       % term selector
    ;       typesel(mer_type).          % type selector

    % Type to represent the sharing information that is manually added
    % to procedures implemented as foreign_procs.
    %
:- type user_annotated_sharing
    --->    no_user_annotated_sharing
    ;       user_sharing(
                sharing     ::  structure_sharing_domain,
                maybe_types ::  maybe(user_sharing_type_information)
            ).

    % The user may have declared the sharing in terms of type variables. In
    % that case, we record the types, and the type variable set.
    %
:- type user_sharing_type_information
    --->    user_type_info(
                types       ::  list(mer_type),
                typevarset  ::  tvarset
            ).

%---------------------------------------------------------------------------%
%
% Stuff for the `structure_reuse_info' pragma.
%

:- interface.

:- type dead_var == prog_var.
:- type dead_datastruct == datastruct.
:- type dead_datastructs == set(dead_datastruct).
:- type live_var == prog_var.
:- type set_of_live_var == set_of_progvar.
:- type live_datastruct == datastruct.

    % This is the public representation of the type "reuse_as".
    %
:- type structure_reuse_domain
    --->    has_no_reuse
    ;       has_only_unconditional_reuse
    ;       has_conditional_reuse(list(structure_reuse_condition)).

    % A structure reuse condition specifies all the information needed to
    % verify whether some memory cells can safely be considered as dead at
    % some program point, depending on the calling context.
    % This information consists of three parts:
    %   - a list of dead datastructures specifying which memory cells
    %   might become dead, hence reusable;
    %   - a list of live datastructures that specifies which memory cells
    %   are always live at the place where the above dead datastructures might
    %   become dead;
    %   - a description of the structure sharing existing at the place
    %   where these datastructures might become dead.
    %
:- type structure_reuse_condition
    --->    structure_reuse_condition(
                dead_nodes          :: dead_datastructs,
                local_use_nodes     :: list(live_datastruct),
                local_sharing       :: structure_sharing_domain
            ).

%---------------------------------------------------------------------------%
%
% Stuff for the `unused_args' pragma.
%

:- interface.

    % This `mode_num' type is only used for mode numbers written out in
    % automatically-generated `pragma unused_args' pragmas in `.opt' files.
    % The mode_num gets converted to an HLDS proc_id by make_hlds.m.
    % We don't want to use the `proc_id' type here since the parse tree
    % (prog_data.m and prog_item.m) should not depend on the HLDS.
    %
:- type mode_num == int.

%---------------------------------------------------------------------------%
%
% Stuff for the `exceptions' pragma.
%

:- interface.

:- type exception_status
    --->    will_not_throw
            % This procedure will not throw an exception.

    ;       may_throw(exception_type)
            % This procedure may throw an exception. The exception is
            % classified by the `exception_type' type.

    ;       throw_conditional.
            % Whether the procedure will not throw an exception depends upon
            % the value of one or more polymorphic arguments. XXX This needs
            % to be extended for ho preds. (See exception_analysis.m for
            % more details).

:- type exception_type
    --->    user_exception
            % The exception that might be thrown is of a result of some code
            % calling exception.throw/1.

    ;       type_exception.
            % The exception is a result of a compiler introduced
            % unification/comparison maybe throwing an exception
            % (in the case of user-defined equality or comparison) or
            % propagating an exception from them.

%---------------------------------------------------------------------------%
%
% Stuff for the `format_call' pragma.
%

:- interface.

:- type format_string_values
    --->    format_string_values(
                % The format_call pragma allows users to specify pairs of
                % argument numbers, the first specifying the arg containing
                % the format string, the second the arg containing the
                % values list (as a list of poly_types).

                % The first pair of arguments contain the argument numbers
                % given by the user. The first user-visible arg is arg 1,
                % the second is arg 2, and so on.
                %
                % All error messages generated for invalid argument numbers
                % use this pair.
                fsv_user_fs     :: int,
                fsv_user_vl     :: int,

                % The second pair of arguments is derived from the first pair,
                % but are incremented by the number of any additional
                % type_info and/or typeclass_info arguments added by the
                % polymorphism pass. The code of check_pragma_format_call.m
                % finds the argument types and modes to check by indexing
                % into argument lists using these numbers.
                fsv_cur_fs      :: int,
                fsv_cur_vl      :: int
            ).

%---------------------------------------------------------------------------%
%
% Stuff for the `type_spec' pragma.
%

:- interface.

    % The type substitution for a `pragma type_spec' declaration.
    % Elsewhere in the compiler we generally use the `tsubst' type
    % which is a map rather than (effectively) an assoc_list.
    %
:- type type_subst == one_or_more(tvar_subst).
:- type tvar_subst
    --->    tvar_subst(tvar, mer_type).

%---------------------------------------------------------------------------%
%
% Stuff for the `require_feature_set' pragma.
%

:- interface.

:- type required_feature
    --->    reqf_concurrency
    ;       reqf_single_prec_float
    ;       reqf_double_prec_float
    ;       reqf_memo
    ;       reqf_parallel_conj
    ;       reqf_trailing
    ;       reqf_strict_sequential
    ;       reqf_conservative_gc.

%---------------------------------------------------------------------------%
%
% Require tail recursion pragma.
%

:- interface.

:- type require_tail_recursion
    --->    suppress_tailrec_warnings(
                rtrs_context            :: prog_context
            )
    ;       enable_tailrec_warnings(
                rtre_warn_or_error      :: warning_or_error,
                rtre_recursion_type     :: require_tail_recursion_type,
                rtre_context            :: prog_context
            ).

:- type require_tail_recursion_type
    --->    only_self_recursion_must_be_tail
    ;       both_self_and_mutual_recursion_must_be_tail.

:- pred require_tailrec_type_string(require_tail_recursion_type, string).
:- mode require_tailrec_type_string(in, out) is det.
:- mode require_tailrec_type_string(out, in) is semidet.

:- implementation.

require_tailrec_type_string(only_self_recursion_must_be_tail,
    "self_recursion_only").
require_tailrec_type_string(both_self_and_mutual_recursion_must_be_tail,
    "self_or_mutual_recursion").

%---------------------------------------------------------------------------%
:- end_module parse_tree.prog_data_pragma.
%---------------------------------------------------------------------------%
