%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2003, 2005-2008 The University of Melbourne.
% Copyright (C) 2015, 2018, 2022 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: term_size_prof.m.
% Author: zs.
% Stability: low.
%
% This file is automatically imported into every module when term size
% profiling is enabled. It contains support predicates used for term size
% profiling.
%
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- module term_size_prof_builtin.
:- interface.

    % measure_size(Term, Size): return the size of Term as Size.
    % The cost of the operation is independent of the size of Term;
    % if Term points to the heap, it looks up the size stored at the
    % starts of the cell.
    %
:- pred measure_size(T::in, int::out) is det.

    % measure_size_acc(Term, Size0, Size): similar to measure_size,
    % but instead of returning just the size of term, it returns the
    % size plus Size0.
    %
:- pred measure_size_acc(T::in, int::in, int::out) is det.

    % increment_size(Term, Incr): Term must be a term on the heap
    % that is not fully ground, and whose size slot's contents represents
    % the size of the term in its original binding state. When some of
    % Term's free arguments are bound, we must increment its recorded size
    % by Incr, the sum of the sizes of the terms bound to those arguments.
    % This is what increment_size does. It is impure because it updates
    % Term destructively.
    %
:- impure pred increment_size(T::in, int::in) is det.

    % This function is exactly like int.plus, and is also implemented
    % as a builtin. It is duplicated in this module because only predicates
    % and functions in builtin modules like this one are immune to dead
    % procedure elimination.
    %
:- func term_size_plus(int, int) = int.

    % We want to take measurements only of the top-level invocations of
    % the procedures in the complexity experiment, not the recursive
    % invocations. This type says whether we are already executing the
    % relevant procedure. Its definition should be kept in sync with
    % MR_ComplexityIsActive in runtime/mercury_term_size.h.
:- type complexity_is_active
    --->    is_inactive
    ;       is_active.

    % For each procedure in the complexity experiment, we can take
    % measurements for many different top-level invocations. Values
    % of the complexity_slot type identify one of these invocations.
:- type complexity_slot == int.

:- impure pred complexity_is_active(complexity_is_active::out) is det.

:- impure pred complexity_call_proc(complexity_slot::out) is det.
:- impure pred complexity_exit_proc(complexity_slot::in) is det.
:- impure pred complexity_fail_proc(complexity_slot::in) is failure.
:- impure pred complexity_redo_proc(complexity_slot::in) is failure.

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

:- implementation.

% This module import is required by the Mercury clause for measure_size/2.
%
:- import_module require.

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


:- pragma foreign_decl("C", "
#ifndef MR_TERM_SIZE_PROFILING_GUARD
#define MR_TERM_SIZE_PROFILING_GUARD

  #ifdef MR_RECORD_TERM_SIZES
    #include ""mercury_term_size.h""
  #endif // MR_RECORD_TERM_SIZES

#endif // MR_TERM_SIZE_PROFILING_GUARD
").

:- pragma foreign_proc("C",
    measure_size(Term::in, Size::out),
    [thread_safe, promise_pure, will_not_call_mercury, may_not_duplicate],
"{
#ifdef MR_RECORD_TERM_SIZES
    MR_TypeInfo type_info;

    type_info = (MR_TypeInfo) TypeInfo_for_T;
    Size = MR_term_size(type_info, Term);
  #ifdef MR_DEBUG_TERM_SIZES
    if (MR_heapdebug && MR_lld_print_enabled) {
        printf(""measure_size: %p -> %d\\n"",
            (void *) Term, (int) Size);
    }
  #endif
#else
    MR_fatal_error(""measure_size: term size profiling not enabled"");
#endif
}").

measure_size(_Value, Size) :-
    ( if semidet_succeed then
        error("measure_size: not implemented")
    else
        Size = 0
    ).

:- pragma foreign_proc("C",
    measure_size_acc(Term::in, Size0::in, Size::out),
    [thread_safe, promise_pure, will_not_call_mercury, may_not_duplicate],
"{
#ifdef MR_RECORD_TERM_SIZES
    MR_TypeInfo type_info;

    type_info = (MR_TypeInfo) TypeInfo_for_T;
    Size = MR_term_size(type_info, Term) + Size0;
  #ifdef MR_DEBUG_TERM_SIZES
    if (MR_heapdebug && MR_lld_print_enabled) {
        printf(""measure_size_acc: %p + %d -> %d\\n"",
            (void *) Term, (int) Size0, (int) Size);
    }
  #endif
#else
    MR_fatal_error(""measure_size_acc: term size profiling not enabled"");
#endif
}").

measure_size_acc(_Value, Size0, Size) :-
    ( if semidet_succeed then
        error("measure_size_acc: not implemented")
    else
        Size = Size0
    ).

:- pragma foreign_proc("C",
    increment_size(Term::in, Incr::in),
    [thread_safe, will_not_call_mercury, may_not_duplicate],
"{
#ifdef MR_RECORD_TERM_SIZES
  #ifdef MR_DEBUG_TERM_SIZES
    if (MR_heapdebug && MR_lld_print_enabled) {
        printf(""increment_size: %p + %d\\n"",
            (void *) Term, (int) Incr);
    }
  #endif
    MR_mask_field(Term, -1) += Incr;
#else
    MR_fatal_error(""increment_size: term size profiling not enabled"");
#endif
}").

increment_size(_Value, _Incr) :-
    impure private_builtin.imp.

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

% None of the following predicates are designed to be called directly;
% they are designed only to hang foreign_procs onto.

:- pragma foreign_proc("C",
    complexity_is_active(IsActive::out),
    [thread_safe, will_not_call_mercury],
"
    // Mention IsActive to avoid warning.
    MR_fatal_error(""complexity_mark_active"");
").

complexity_is_active(IsActive) :-
    impure private_builtin.imp,
    ( if semidet_succeed then
        error("complexity_mark_active: not implemented")
    else
        % Required only to avoid warnings; never executed.
        IsActive = is_active
    ).

:- pragma foreign_proc("C",
    complexity_call_proc(Slot::out),
    [thread_safe, will_not_call_mercury],
"
    // Mention Slot to avoid warning.
    MR_fatal_error(""complexity_call_proc"");
").

complexity_call_proc(Slot) :-
    impure private_builtin.imp,
    % Required only to avoid warnings; never executed.
    private_builtin.unsafe_type_cast(0, Slot).

:- pragma foreign_proc("C",
    complexity_exit_proc(Slot::in),
    [thread_safe, will_not_call_mercury],
"
    // Mention Slot to avoid warning.
    MR_fatal_error(""complexity_exit_proc"");
").

complexity_exit_proc(_Slot) :-
    impure private_builtin.imp.

:- pragma foreign_proc("C",
    complexity_fail_proc(Slot::in),
    [thread_safe, will_not_call_mercury],
"
    // Mention Slot to avoid warning.
    MR_fatal_error(""complexity_fail_proc"");
").

complexity_fail_proc(_Slot) :-
    impure private_builtin.imp,
    fail.

:- pragma foreign_proc("C",
    complexity_redo_proc(Slot::in),
    [thread_safe, will_not_call_mercury],
"
    // Mention Slot to avoid warning.
    MR_fatal_error(""complexity_redo_proc"");
").

complexity_redo_proc(_Slot) :-
    impure private_builtin.imp,
    fail.

%---------------------------------------------------------------------------%
:- end_module term_size_prof_builtin.
%---------------------------------------------------------------------------%
