//==----------------------- kernel_desc.hpp --------------------------------==//
//
// 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
//
//===----------------------------------------------------------------------===//

#pragma once

// FIXME: include export.hpp because integration header emitted by the compiler
// uses the macro defined in this header, but it doesn't explicitly include it.
#include <sycl/detail/defines_elementary.hpp>
#include <sycl/detail/export.hpp>
// This header file must not include any standard C++ header files.

#ifndef __INTEL_SYCL_USE_INTEGRATION_HEADERS
#if __has_builtin(__builtin_sycl_kernel_name)
static_assert(__has_builtin(__builtin_sycl_kernel_param_count) &&
              __has_builtin(__builtin_sycl_kernel_name) &&
              __has_builtin(__builtin_sycl_kernel_param_access_target) &&
              __has_builtin(__builtin_sycl_kernel_param_size) &&
              __has_builtin(__builtin_sycl_kernel_param_offset) &&
              __has_builtin(__builtin_sycl_kernel_file_name) &&
              __has_builtin(__builtin_sycl_kernel_function_name) &&
              __has_builtin(__builtin_sycl_kernel_line_number) &&
              __has_builtin(__builtin_sycl_kernel_column_number));
#else
#define __INTEL_SYCL_USE_INTEGRATION_HEADERS 1
#endif
#endif

namespace sycl {
inline namespace _V1 {
namespace detail {

#ifndef __SYCL_DEVICE_ONLY__
#define _Bool bool
#endif

// As stated above, this header file cannot include any of the standard C++
// headers. We need int64_t.  Here we are matching the exact definition used by
// the SemaSYCL version of kernel_desc.hpp in the FE.
template <bool Cond, typename TrueT, typename FalseT> struct conditional {
  using type = TrueT;
};
template <typename TrueT, typename FalseT>
struct conditional<false, TrueT, FalseT> {
  using type = FalseT;
};
using int64_t = conditional<sizeof(long) == 8, long, long long>::type;

// kernel parameter kinds
enum class kernel_param_kind_t {
  kind_accessor = 0,
  kind_std_layout = 1, // standard layout object parameters
  kind_sampler = 2,
  kind_pointer = 3,
  kind_specialization_constants_buffer = 4,
  kind_stream = 5,
  kind_work_group_memory = 6,
  kind_dynamic_work_group_memory = 7,
  kind_dynamic_accessor = 8,
  kind_struct_with_special_type = 9, // structs that contain special types
  kind_invalid = 0xf,                // not a valid kernel kind
};

// describes a kernel parameter
struct kernel_param_desc_t {
  // parameter kind
  kernel_param_kind_t kind;
  // kind == kind_std_layout
  //   parameter size in bytes (includes padding for structs)
  // kind == kind_accessor
  //   access target; possible access targets are defined in access/access.hpp
  int info;
  // offset of the captured value of the parameter in the lambda or function
  // object
  int offset;
};

// Translates specialization constant type to its name.
template <class Name> struct SpecConstantInfo {
  static constexpr const char *getName() { return ""; }
};

// Translates SYCL 2020 `specialization_id` to a unique symbolic identifier.
// There are no primary definition, only specializations in the integration
// footer.
template <auto &SpecName> const char *get_spec_constant_symbolic_ID_impl();
// Wrapper is needed to delay instantiation of
// 'get_spec_constant_symbolic_ID_impl' until after we have encountered all
// specializations for it generated by the compiler in the integration footer.
// Definition in spec_const_integration.hpp.
template <auto &SpecName> const char *get_spec_constant_symbolic_ID();

#ifndef __SYCL_UNNAMED_LAMBDA__
template <class KernelNameType> struct KernelInfo {
  static constexpr unsigned getNumParams() { return 0; }
  static constexpr const kernel_param_desc_t &getParamDesc(int) {
    return Dummy;
  }
  static constexpr const char *getName() { return ""; }
  static constexpr bool isESIMD() { return 0; }
  static constexpr const char *getFileName() { return ""; }
  static constexpr const char *getFunctionName() { return ""; }
  static constexpr unsigned getLineNumber() { return 0; }
  static constexpr unsigned getColumnNumber() { return 0; }
  static constexpr int64_t getKernelSize() { return 0; }

private:
  static constexpr kernel_param_desc_t Dummy{};
};
#else
template <char...> struct KernelInfoData {
  static constexpr unsigned getNumParams() { return 0; }
  static constexpr const kernel_param_desc_t &getParamDesc(int) {
    return Dummy;
  }
  static constexpr const char *getName() { return ""; }
  static constexpr bool isESIMD() { return 0; }
  static constexpr const char *getFileName() { return ""; }
  static constexpr const char *getFunctionName() { return ""; }
  static constexpr unsigned getLineNumber() { return 0; }
  static constexpr unsigned getColumnNumber() { return 0; }
  static constexpr int64_t getKernelSize() { return 0; }

private:
  static constexpr kernel_param_desc_t Dummy{};
};

// C++14 like index_sequence and make_index_sequence
// not needed C++14 members (value_type, size) not implemented
template <class T, T...> struct integer_sequence {};
template <unsigned long long... I>
using index_sequence = integer_sequence<unsigned long long, I...>;
template <unsigned long long N>
using make_index_sequence =
    __make_integer_seq<integer_sequence, unsigned long long, N>;

template <typename T> struct KernelInfoImpl {
private:
  static constexpr auto n = __builtin_sycl_unique_stable_name(T);
  template <unsigned long long... I>
  static KernelInfoData<n[I]...> impl(index_sequence<I...>) {
    return {};
  }

public:
  using type = decltype(impl(make_index_sequence<__builtin_strlen(n)>{}));
};

// For named kernels, this structure is specialized in the integration header.
// For unnamed kernels, KernelInfoData is specialized in the integration header,
// and this picks it up via the KernelInfoImpl. For non-existent kernels, this
// will also pick up a KernelInfoData (as SubKernelInfo) via KernelInfoImpl, but
// it will instead get the unspecialized case, defined above.
template <class KernelNameType> struct KernelInfo {
  using SubKernelInfo = typename KernelInfoImpl<KernelNameType>::type;
  static constexpr unsigned getNumParams() {
    return SubKernelInfo::getNumParams();
  }
  static constexpr const kernel_param_desc_t &getParamDesc(int Idx) {
    return SubKernelInfo::getParamDesc(Idx);
  }
  static constexpr const char *getName() { return SubKernelInfo::getName(); }
  static constexpr bool isESIMD() { return SubKernelInfo::isESIMD(); }
  static constexpr const char *getFileName() { return ""; }
  static constexpr const char *getFunctionName() { return ""; }
  static constexpr unsigned getLineNumber() { return 0; }
  static constexpr unsigned getColumnNumber() { return 0; }
  static constexpr int64_t getKernelSize() {
    return SubKernelInfo::getKernelSize();
  }
};
#endif //__SYCL_UNNAMED_LAMBDA__

template <auto *Func> struct FreeFunctionInfoData {
  static constexpr unsigned getNumParams() { return 0; }
  static constexpr const char *getFunctionName() { return ""; }
};

// Built-ins accept an object due to lacking infrastructure support for
// accepting types. The kernel name type itself isn't used because it might be
// incomplete, cv-qualified, or not default constructible. Passing an object
// also allows future extension for SYCL kernels defined as free functions.
template <typename KNT> struct KernelIdentity {
  using type = KNT;
};

} // namespace detail
} // namespace _V1
} // namespace sycl
