//===--- Level Zero Target RTL Implementation -----------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Level Zero RTL Options support.
//
//===----------------------------------------------------------------------===//

#include "omptarget.h"

#include "L0Defs.h"
#include "L0Options.h"
#include "L0Trace.h"

namespace llvm::omp::target::plugin {

/// Read environment variables.
void L0OptionsTy::processEnvironmentVars() {
  // Compilation options for IGC.
  UserCompilationOptions +=
      std::string(" ") +
      StringEnvar("LIBOMPTARGET_LEVEL_ZERO_COMPILATION_OPTIONS", "").get();

  Flags.UseCopyOffloadHint =
      BoolEnvar("LIBOFFLOAD_LEVEL_ZERO_USE_COPY_OFFLOAD_HINT", true);

  // Memory pool syntax:
  // LIBOMPTARGET_LEVEL_ZERO_MEMORY_POOL=<Option>
  //  <Option>       := 0 | <PoolInfoList>
  //  <PoolInfoList> := <PoolInfo>[,<PoolInfoList>]
  //  <PoolInfo>     := <MemType>[,<AllocMax>[,<Capacity>[,<PoolSize>]]]
  //  <MemType>      := all | device | host | shared
  //  <AllocMax>     := non-negative integer or empty, max allocation size in
  //                    MB (default: 1).
  //  <Capacity>     := positive integer or empty, number of allocations from
  //                    a single block (default: 4).
  //  <PoolSize>     := positive integer or empty, max pool size in MB
  //                    (default: 256).
  const StringEnvar MemoryPoolVar("LIBOMPTARGET_LEVEL_ZERO_MEMORY_POOL", "");
  if (MemoryPoolVar.isPresent()) {
    if (MemoryPoolVar.get() == "0") {
      Flags.UseMemoryPool = 0;
      MemPoolConfig.fill({false, 0, 0, 0});
    } else {
      std::istringstream Str(MemoryPoolVar.get());
      int32_t MemType = -1;
      int32_t Offset = 0;
      int32_t Valid = 1;
      constexpr std::array<int32_t, 3> DefaultValue{1, 4, 256};
      constexpr int32_t AllMemType =
          std::numeric_limits<decltype(AllMemType)>::max();
      std::array<int32_t, 3> AllInfo{1, 4, 256};
      std::array<std::array<int32_t, 3>, 3> PoolInfo;
      PoolInfo.fill({-1, 0, 0});
      for (std::string Token; std::getline(Str, Token, ',') && Valid > 0;) {
        if (Token == "device") {
          MemType = TARGET_ALLOC_DEVICE;
          PoolInfo[TARGET_ALLOC_DEVICE] = DefaultValue;
          Offset = 0;
        } else if (Token == "host") {
          MemType = TARGET_ALLOC_HOST;
          PoolInfo[TARGET_ALLOC_HOST] = DefaultValue;
          Offset = 0;
        } else if (Token == "shared") {
          MemType = TARGET_ALLOC_SHARED;
          PoolInfo[TARGET_ALLOC_SHARED] = DefaultValue;
          Offset = 0;
        } else if (Token == "all") {
          MemType = AllMemType;
          Offset = 0;
          Valid = 2;
        } else if (Offset < 3 && MemType >= 0) {
          int32_t Num = std::atoi(Token.c_str());
          bool ValidNum = (Num >= 0 && Offset == 0) || (Num > 0 && Offset > 0);
          if (ValidNum && MemType == AllMemType)
            AllInfo[Offset++] = Num;
          else if (ValidNum)
            PoolInfo[MemType][Offset++] = Num;
          else if (Token.size() == 0)
            Offset++;
          else
            Valid = 0;
        } else {
          Valid = 0;
        }
      }
      if (Valid > 0) {
        if (Valid == 2) {
          // "all" is specified -- ignore other inputs.
          if (AllInfo[0] > 0) {
            MemPoolConfig[TARGET_ALLOC_DEVICE] = {true, AllInfo[0], AllInfo[1],
                                                  AllInfo[2]};
            MemPoolConfig[TARGET_ALLOC_HOST] = {true, AllInfo[0], AllInfo[1],
                                                AllInfo[2]};
            MemPoolConfig[TARGET_ALLOC_SHARED] = {true, AllInfo[0], AllInfo[1],
                                                  AllInfo[2]};
          } else {
            MemPoolConfig.fill({false, 0, 0, 0});
          }
        } else {
          for (size_t Pool = 0; Pool < PoolInfo.size(); ++Pool) {
            switch (PoolInfo[Pool][0]) {
            case -1:
              // No value was specified, keep the default.
              break;
            case 0:
              // Pool was disabled.
              MemPoolConfig[Pool] = {false, 0, 0, 0};
              break;
            default:
              // Use the user specified values.
              MemPoolConfig[Pool] = {true, PoolInfo[Pool][0], PoolInfo[Pool][1],
                                     PoolInfo[Pool][2]};
              break;
            }
          }
        }
      } else {
        ODBG_OS(OLDT_Init, [&](llvm::raw_ostream &O) {
          O << "Ignoring incorrect memory pool configuration "
               "LIBOMPTARGET_LEVEL_ZERO_MEMORY_POOL="
            << MemoryPoolVar.get() << "\n";
          O << "LIBOMPTARGET_LEVEL_ZERO_MEMORY_POOL=<Option>\n";
          O << "  <Option>       := 0 | <PoolInfoList>\n";
          O << "  <PoolInfoList> := <PoolInfo>[,<PoolInfoList>]\n";
          O << "  <PoolInfo>     := "
               "<MemType>[,<AllocMax>[,<Capacity>[,<PoolSize>]]]\n";
          O << "  <MemType>      := all | device | host | shared\n";
          O << "  <AllocMax>     := non-negative integer or empty, "
               "max allocation size in MB (default: 1)\n";
          O << "  <Capacity>     := positive integer or empty, "
               "number of allocations from a single block (default: 4)\n";
          O << "  <PoolSize>     := positive integer or empty, "
               "max pool size in MB (default: 256)\n";
        });
      }
    }
  }

  if (StringEnvar("INTEL_ENABLE_OFFLOAD_ANNOTATIONS").isPresent()) {
    // To match SYCL RT behavior, we just need to check whether
    // INTEL_ENABLE_OFFLOAD_ANNOTATIONS is set. The actual value
    // does not matter.
    CommonSpecConstants.addConstant<char>(0xFF747469, 1);
  }

  // LIBOMPTARGET_LEVEL_ZERO_STAGING_BUFFER_SIZE=<SizeInKB>.
  const Envar<size_t> StagingBufferSizeVar(
      "LIBOMPTARGET_LEVEL_ZERO_STAGING_BUFFER_SIZE");
  if (StagingBufferSizeVar.isPresent()) {
    size_t SizeInKB = StagingBufferSizeVar;
    if (SizeInKB > (16 << 10)) {
      SizeInKB = (16 << 10);
      ODBG(OLDT_Init) << "Staging buffer size is capped at " << SizeInKB
                      << " KB";
    }
    StagingBufferSize = SizeInKB << 10;
  }

  // LIBOMPTARGET_LEVEL_ZERO_COMMAND_MODE=<Fmt>.
  // <Fmt> := sync | async | async_ordered
  // sync: perform synchronization after each command.
  // async: perform synchronization when it is required.
  // async_ordered: same as "async", but command is ordered.
  // This option is ignored unless IMM is fully enabled on compute and copy.
  // On Intel PVC GPU, when used with immediate command lists over Level Zero
  // backend, a target region may involve multiple command submissions to the
  // L0 copy queue and compute queue. L0 events are used for each submission
  // (data transfer of a single item or kernel execution). When "async" is
  // specified, a) each data transfer to device is submitted with an event.
  // b) The kernel is submitted next with a dependence on all the previous
  // data transfer events. The kernel also has an event associated with it.
  // c) The data transfer from device will be submitted with a dependence on
  // the kernel event. d) Finally wait on the host for all the events
  // associated with the data transfer from device.
  // The env-var also affects any "target update" constructs as well.
  // The env-var only affects the L0 copy/  compute commands issued from a
  // single target construct execution, not across multiple invocations.
  const StringEnvar CommandModeVar("LIBOMPTARGET_LEVEL_ZERO_COMMAND_MODE");
  if (CommandModeVar.isPresent()) {
    if (match(CommandModeVar, "sync"))
      CommandMode = CommandModeTy::Sync;
    else if (match(CommandModeVar, "async"))
      CommandMode = CommandModeTy::Async;
    else if (match(CommandModeVar, "async_ordered"))
      CommandMode = CommandModeTy::AsyncOrdered;
    else if (match(CommandModeVar, "inorder"))
      CommandMode = CommandModeTy::InOrder;
    else
      MESSAGE("Warning: Ignoring invalid value for "
              "LIBOMPTARGET_LEVEL_ZERO_COMMAND_MODE=%s\n",
              CommandModeVar.get().c_str());
  }

  // Detect if we need to enable compatibility with Level Zero debug mode.
  ZeDebugEnabled = BoolEnvar("ZET_ENABLE_PROGRAM_DEBUGGING", false);
}

} // namespace llvm::omp::target::plugin
