
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "kmp.h"
#include "kmp_i18n.h"
#include "kmp_io.h"
#include "kmp_str.h"
#if OMPT_SUPPORT
#include "ompt-specific.h"
#endif

/*!
@ingroup CANCELLATION
@param loc_ref location of the original task directive
@param gtid Global thread ID of encountering thread
@param cncl_kind Cancellation kind (parallel, for, sections, taskgroup)

@return returns true if the cancellation request has been activated and the
execution thread needs to proceed to the end of the canceled region.

Request cancellation of the binding OpenMP region.
*/
kmp_int32 __kmpc_cancel(ident_t *loc_ref, kmp_int32 gtid, kmp_int32 cncl_kind) {
  kmp_info_t *this_thr = __kmp_threads[gtid];

  KC_TRACE(10, ("__kmpc_cancel: T#%d request %d OMP_CANCELLATION=%d\n", gtid,
                cncl_kind, __kmp_omp_cancellation));

  KMP_DEBUG_ASSERT(cncl_kind != cancel_noreq);
  KMP_DEBUG_ASSERT(cncl_kind == cancel_parallel || cncl_kind == cancel_loop ||
                   cncl_kind == cancel_sections ||
                   cncl_kind == cancel_taskgroup);
  KMP_DEBUG_ASSERT(__kmp_get_gtid() == gtid);

  if (__kmp_omp_cancellation) {
    switch (cncl_kind) {
    case cancel_parallel:
    case cancel_loop:
    case cancel_sections:
      // cancellation requests for parallel and worksharing constructs
      // are handled through the team structure
      {
        kmp_team_t *this_team = this_thr->th.th_team;
        KMP_DEBUG_ASSERT(this_team);
        kmp_int32 old = cancel_noreq;
        this_team->t.t_cancel_request.compare_exchange_strong(old, cncl_kind);
        if (old == cancel_noreq || old == cncl_kind) {
// we do not have a cancellation request in this team or we do have
// one that matches the current request -> cancel
#if OMPT_SUPPORT && OMPT_OPTIONAL
          if (ompt_enabled.ompt_callback_cancel) {
            ompt_data_t *task_data;
            __ompt_get_task_info_internal(0, NULL, &task_data, NULL, NULL,
                                          NULL);
            ompt_cancel_flag_t type = ompt_cancel_parallel;
            if (cncl_kind == cancel_parallel)
              type = ompt_cancel_parallel;
            else if (cncl_kind == cancel_loop)
              type = ompt_cancel_loop;
            else if (cncl_kind == cancel_sections)
              type = ompt_cancel_sections;
            ompt_callbacks.ompt_callback(ompt_callback_cancel)(
                task_data, type | ompt_cancel_activated,
                OMPT_GET_RETURN_ADDRESS(0));
          }
#endif // OMPT_SUPPORT && OMPT_OPTIONAL
          return 1 /* true */;
        }
        break;
      }
    case cancel_taskgroup:
      // cancellation requests for a task group
      // are handled through the taskgroup structure
      {
        kmp_taskdata_t *task;
        kmp_taskgroup_t *taskgroup;

        task = this_thr->th.th_current_task;
        KMP_DEBUG_ASSERT(task);

        taskgroup = task->td_taskgroup;
        if (taskgroup) {
          kmp_int32 old = cancel_noreq;
          taskgroup->cancel_request.compare_exchange_strong(old, cncl_kind);
          if (old == cancel_noreq || old == cncl_kind) {
// we do not have a cancellation request in this taskgroup or we do
// have one that matches the current request -> cancel
#if OMPT_SUPPORT && OMPT_OPTIONAL
            if (ompt_enabled.ompt_callback_cancel) {
              ompt_data_t *task_data;
              __ompt_get_task_info_internal(0, NULL, &task_data, NULL, NULL,
                                            NULL);
              ompt_callbacks.ompt_callback(ompt_callback_cancel)(
                  task_data, ompt_cancel_taskgroup | ompt_cancel_activated,
                  OMPT_GET_RETURN_ADDRESS(0));
            }
#endif
            return 1 /* true */;
          }
        } else {
          // TODO: what needs to happen here?
          // the specification disallows cancellation w/o taskgroups
          // so we might do anything here, let's abort for now
          KMP_ASSERT(0 /* false */);
        }
      }
      break;
    default:
      KMP_ASSERT(0 /* false */);
    }
  }

  // ICV OMP_CANCELLATION=false, so we ignored this cancel request
  KMP_DEBUG_ASSERT(!__kmp_omp_cancellation);
  return 0 /* false */;
}

/*!
@ingroup CANCELLATION
@param loc_ref location of the original task directive
@param gtid Global thread ID of encountering thread
@param cncl_kind Cancellation kind (parallel, for, sections, taskgroup)

@return returns true if a matching cancellation request has been flagged in the
RTL and the encountering thread has to cancel..

Cancellation point for the encountering thread.
*/
kmp_int32 __kmpc_cancellationpoint(ident_t *loc_ref, kmp_int32 gtid,
                                   kmp_int32 cncl_kind) {
  kmp_info_t *this_thr = __kmp_threads[gtid];

  KC_TRACE(10,
           ("__kmpc_cancellationpoint: T#%d request %d OMP_CANCELLATION=%d\n",
            gtid, cncl_kind, __kmp_omp_cancellation));

  KMP_DEBUG_ASSERT(cncl_kind != cancel_noreq);
  KMP_DEBUG_ASSERT(cncl_kind == cancel_parallel || cncl_kind == cancel_loop ||
                   cncl_kind == cancel_sections ||
                   cncl_kind == cancel_taskgroup);
  KMP_DEBUG_ASSERT(__kmp_get_gtid() == gtid);

  if (__kmp_omp_cancellation) {
    switch (cncl_kind) {
    case cancel_parallel:
    case cancel_loop:
    case cancel_sections:
      // cancellation requests for parallel and worksharing constructs
      // are handled through the team structure
      {
        kmp_team_t *this_team = this_thr->th.th_team;
        KMP_DEBUG_ASSERT(this_team);
        if (this_team->t.t_cancel_request) {
          if (cncl_kind == this_team->t.t_cancel_request) {
// the request in the team structure matches the type of
// cancellation point so we can cancel
#if OMPT_SUPPORT && OMPT_OPTIONAL
            if (ompt_enabled.ompt_callback_cancel) {
              ompt_data_t *task_data;
              __ompt_get_task_info_internal(0, NULL, &task_data, NULL, NULL,
                                            NULL);
              ompt_cancel_flag_t type = ompt_cancel_parallel;
              if (cncl_kind == cancel_parallel)
                type = ompt_cancel_parallel;
              else if (cncl_kind == cancel_loop)
                type = ompt_cancel_loop;
              else if (cncl_kind == cancel_sections)
                type = ompt_cancel_sections;
              ompt_callbacks.ompt_callback(ompt_callback_cancel)(
                  task_data, type | ompt_cancel_detected,
                  OMPT_GET_RETURN_ADDRESS(0));
            }
#endif
            return 1 /* true */;
          }
          KMP_ASSERT(0 /* false */);
        } else {
          // we do not have a cancellation request pending, so we just
          // ignore this cancellation point
          return 0;
        }
        break;
      }
    case cancel_taskgroup:
      // cancellation requests for a task group
      // are handled through the taskgroup structure
      {
        kmp_taskdata_t *task;
        kmp_taskgroup_t *taskgroup;

        task = this_thr->th.th_current_task;
        KMP_DEBUG_ASSERT(task);

        taskgroup = task->td_taskgroup;
        if (taskgroup) {
// return the current status of cancellation for the taskgroup
#if OMPT_SUPPORT && OMPT_OPTIONAL
          if (ompt_enabled.ompt_callback_cancel &&
              !!taskgroup->cancel_request) {
            ompt_data_t *task_data;
            __ompt_get_task_info_internal(0, NULL, &task_data, NULL, NULL,
                                          NULL);
            ompt_callbacks.ompt_callback(ompt_callback_cancel)(
                task_data, ompt_cancel_taskgroup | ompt_cancel_detected,
                OMPT_GET_RETURN_ADDRESS(0));
          }
#endif
          return !!taskgroup->cancel_request;
        } else {
          // if a cancellation point is encountered by a task that does not
          // belong to a taskgroup, it is OK to ignore it
          return 0 /* false */;
        }
      }
    default:
      KMP_ASSERT(0 /* false */);
    }
  }

  // ICV OMP_CANCELLATION=false, so we ignore the cancellation point
  KMP_DEBUG_ASSERT(!__kmp_omp_cancellation);
  return 0 /* false */;
}

/*!
@ingroup CANCELLATION
@param loc_ref location of the original task directive
@param gtid Global thread ID of encountering thread

@return returns true if a matching cancellation request has been flagged in the
RTL and the encountering thread has to cancel..

Barrier with cancellation point to send threads from the barrier to the
end of the parallel region.  Needs a special code pattern as documented
in the design document for the cancellation feature.
*/
kmp_int32 __kmpc_cancel_barrier(ident_t *loc, kmp_int32 gtid) {
  int ret = 0 /* false */;
  kmp_info_t *this_thr = __kmp_threads[gtid];
  kmp_team_t *this_team = this_thr->th.th_team;

  KMP_DEBUG_ASSERT(__kmp_get_gtid() == gtid);

  // call into the standard barrier
  __kmpc_barrier(loc, gtid);

  // if cancellation is active, check cancellation flag
  if (__kmp_omp_cancellation) {
    // depending on which construct to cancel, check the flag and
    // reset the flag
    switch (KMP_ATOMIC_LD_RLX(&(this_team->t.t_cancel_request))) {
    case cancel_parallel:
      ret = 1;
      // ensure that threads have checked the flag, when
      // leaving the above barrier
      __kmpc_barrier(loc, gtid);
      this_team->t.t_cancel_request = cancel_noreq;
      // the next barrier is the fork/join barrier, which
      // synchronizes the threads leaving here
      break;
    case cancel_loop:
    case cancel_sections:
      ret = 1;
      // ensure that threads have checked the flag, when
      // leaving the above barrier
      __kmpc_barrier(loc, gtid);
      this_team->t.t_cancel_request = cancel_noreq;
      // synchronize the threads again to make sure we do not have any run-away
      // threads that cause a race on the cancellation flag
      __kmpc_barrier(loc, gtid);
      break;
    case cancel_taskgroup:
      // this case should not occur
      KMP_ASSERT(0 /* false */);
      break;
    case cancel_noreq:
      // do nothing
      break;
    default:
      KMP_ASSERT(0 /* false */);
    }
  }

  return ret;
}

/*!
@ingroup CANCELLATION
@param loc_ref location of the original task directive
@param gtid Global thread ID of encountering thread

@return returns true if a matching cancellation request has been flagged in the
RTL and the encountering thread has to cancel..

Query function to query the current status of cancellation requests.
Can be used to implement the following pattern:

if (kmp_get_cancellation_status(kmp_cancel_parallel)) {
    perform_cleanup();
    #pragma omp cancellation point parallel
}
*/
int __kmp_get_cancellation_status(int cancel_kind) {
  if (__kmp_omp_cancellation) {
    kmp_info_t *this_thr = __kmp_entry_thread();

    switch (cancel_kind) {
    case cancel_parallel:
    case cancel_loop:
    case cancel_sections: {
      kmp_team_t *this_team = this_thr->th.th_team;
      return this_team->t.t_cancel_request == cancel_kind;
    }
    case cancel_taskgroup: {
      kmp_taskdata_t *task;
      kmp_taskgroup_t *taskgroup;
      task = this_thr->th.th_current_task;
      taskgroup = task->td_taskgroup;
      return taskgroup && taskgroup->cancel_request;
    }
    }
  }

  return 0 /* false */;
}
