%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1994-2012 The University of Melbourne.
% Copyright (C) 2015, 2024 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.
%---------------------------------------------------------------------------%
%
% File: mode_top_functor.m.
%
% This module computes top_functor_modes.
%
%---------------------------------------------------------------------------%

:- module check_hlds.mode_top_functor.
:- interface.

:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.

:- import_module list.

%---------------------------------------------------------------------------%
%
% Computing top_functor_modes.
%

    % mode_to_top_functor_mode converts a mode (and corresponding type)
    % to a top_functor_mode.
    %
    % A mode is a high-level notion, the normal Mercury language mode.
    % A top_functor_mode is a low-level notion used for code generation,
    % which indicates the argument passing convention (top_in, top_out, or
    % top_unused) that corresponds to that mode. We need to know the type,
    % not just the mode, because the argument passing convention can depend
    % on the type's representation.
    %
:- pred mode_to_top_functor_mode(module_info::in, mer_mode::in,
    mer_type::in, top_functor_mode::out) is det.
:- pred init_final_insts_to_top_functor_mode(module_info::in,
    mer_inst::in, mer_inst::in, mer_type::in, top_functor_mode::out) is det.

    % Zip together the given lists of modes and types, and return
    % the top_functor_mode computed by mode_to_top_functor_mode
    % for each pair.
    %
:- pred modes_to_top_functor_modes(module_info::in,
    list(mer_mode)::in, list(mer_type)::in, list(top_functor_mode)::out)
    is det.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- implementation.

:- import_module check_hlds.inst_lookup.
:- import_module check_hlds.inst_test.
:- import_module check_hlds.mode_util.
:- import_module check_hlds.type_util.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.prog_type.

:- import_module require.
:- import_module term.

%---------------------------------------------------------------------------%

mode_to_top_functor_mode(ModuleInfo, Mode, Type, TopFunctorMode) :-
    mode_get_insts(ModuleInfo, Mode, InitialInst, FinalInst),
    find_top_functor_mode_loop_over_notags(ModuleInfo, Type, [],
        InitialInst, FinalInst, TopFunctorMode).

init_final_insts_to_top_functor_mode(ModuleInfo, InitialInst, FinalInst, Type,
        TopFunctorMode) :-
    find_top_functor_mode_loop_over_notags(ModuleInfo, Type, [],
        InitialInst, FinalInst, TopFunctorMode).

:- pred find_top_functor_mode_loop_over_notags(module_info::in,
    mer_type::in, list(type_ctor)::in, mer_inst::in, mer_inst::in,
    top_functor_mode::out) is det.

find_top_functor_mode_loop_over_notags(ModuleInfo, Type, ContainingTypes,
        InitialInst, FinalInst, TopFunctorMode) :-
    % We need to handle no_tag types (types which have exactly one constructor,
    % and whose one constructor has exactly one argument) specially here,
    % since for them an inst of bound(f(free)) is not really bound as far as
    % code generation is concerned, since the f/1 will get optimized away.
    ( if
        % Is this a no_tag type?
        type_is_no_tag_type(ModuleInfo, Type, FunctorName, ArgType),
        % Avoid infinite recursion.
        type_to_ctor(Type, TypeCtor),
        not list.member(TypeCtor, ContainingTypes)
    then
        % The top_functor_mode will be determined by the mode and type of the
        % functor's argument, so we figure out the mode and type of the
        % argument, and then recurse.

        ConsId = du_data_ctor(du_ctor(FunctorName, 1, TypeCtor)),
        get_single_arg_inst(ModuleInfo, InitialInst, ConsId, InitialArgInst),
        get_single_arg_inst(ModuleInfo, FinalInst, ConsId, FinalArgInst),
        find_top_functor_mode_loop_over_notags(ModuleInfo,
            ArgType, [TypeCtor | ContainingTypes],
            InitialArgInst, FinalArgInst, TopFunctorMode)
    else
        ( if inst_is_bound(ModuleInfo, InitialInst) then
            TopFunctorMode = top_in
        else if inst_is_bound(ModuleInfo, FinalInst) then
            TopFunctorMode = top_out
        else
            TopFunctorMode = top_unused
        )
    ).

    % get_single_arg_inst(ModuleInfo, Inst, ConsId, ArgInsts):
    %
    % Given an inst `Inst', figure out what the inst of the argument would be,
    % assuming that the functor is the one given by the specified ConsId,
    % whose arity is 1.
    %
:- pred get_single_arg_inst(module_info::in, mer_inst::in, cons_id::in,
    mer_inst::out) is det.

get_single_arg_inst(ModuleInfo, Inst, ConsId, ArgInst) :-
    % XXX This is very similar to get_arg_insts in prog_mode.
    (
        Inst = defined_inst(InstName),
        inst_lookup(ModuleInfo, InstName, NamedInst),
        get_single_arg_inst(ModuleInfo, NamedInst, ConsId, ArgInst)
    ;
        Inst = not_reached,
        ArgInst = not_reached
    ;
        Inst = ground(Uniq, _PredInst),
        ArgInst = ground(Uniq, none_or_default_func)
    ;
        Inst = bound(_Uniq, _InstResult, BoundFunctors),
        ( if
            get_single_arg_inst_in_bound_functors(BoundFunctors, ConsId,
                ArgInst0)
        then
            ArgInst = ArgInst0
        else
            % The code is unreachable.
            ArgInst = not_reached
        )
    ;
        Inst = free,
        ArgInst = free
    ;
        Inst = any(Uniq, _),
        ArgInst = any(Uniq, none_or_default_func)
    ;
        Inst = inst_var(_),
        unexpected($pred, "inst_var")
    ;
        Inst = constrained_inst_vars(_, InsideInst),
        get_single_arg_inst(ModuleInfo, InsideInst, ConsId, ArgInst)
    ).

:- pred get_single_arg_inst_in_bound_functors(list(bound_functor)::in,
    cons_id::in, mer_inst::out) is semidet.

get_single_arg_inst_in_bound_functors([BoundFunctor | BoundFunctors], ConsId,
        ArgInst) :-
    ( if
        BoundFunctor = bound_functor(InstConsId, [ArgInst0]),
        % The cons_ids for types and insts can differ in the type_ctor field
        % so we must ignore them.
        equivalent_cons_ids(ConsId, InstConsId)
    then
        ArgInst = ArgInst0
    else
        get_single_arg_inst_in_bound_functors(BoundFunctors, ConsId, ArgInst)
    ).

modes_to_top_functor_modes(_ModuleInfo, [], [], []).
modes_to_top_functor_modes(_ModuleInfo, [], [_ | _], _) :-
        unexpected($pred, "length mismatch").
modes_to_top_functor_modes(_ModuleInfo, [_ | _], [], _) :-
        unexpected($pred, "length mismatch").
modes_to_top_functor_modes(ModuleInfo, [Mode | Modes], [Type | Types],
        [TopFunctorMode | TopFunctorModes]) :-
    mode_to_top_functor_mode(ModuleInfo, Mode, Type, TopFunctorMode),
    modes_to_top_functor_modes(ModuleInfo, Modes, Types, TopFunctorModes).

%---------------------------------------------------------------------------%
:- end_module check_hlds.mode_top_functor.
%---------------------------------------------------------------------------%
