//===--- OpenCLOptions.h ----------------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Defines the clang::OpenCLOptions class.
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_BASIC_OPENCLOPTIONS_H
#define LLVM_CLANG_BASIC_OPENCLOPTIONS_H

#include "clang/Basic/LangOptions.h"
#include "llvm/ADT/StringMap.h"

namespace clang {

/// OpenCL supported extensions and optional core features
class OpenCLOptions {
  struct Info {
    bool Supported; // Is this option supported
    bool Enabled;   // Is this option enabled
    unsigned Avail; // Option starts to be available in this OpenCL version
    unsigned Core;  // Option becomes (optional) core feature in this OpenCL
                    // version
    Info(bool S = false, bool E = false, unsigned A = 100, unsigned C = ~0U)
      :Supported(S), Enabled(E), Avail(A), Core(C){}
  };
  llvm::StringMap<Info> OptMap;
public:
  bool isKnown(llvm::StringRef Ext) const {
    return OptMap.find(Ext) != OptMap.end();
  }

  bool isEnabled(llvm::StringRef Ext) const {
    return OptMap.find(Ext)->second.Enabled;
  }

  // Is supported as either an extension or an (optional) core feature for
  // OpenCL version \p CLVer.
  bool isSupported(llvm::StringRef Ext, const LangOptions &LO) const {
    // In C++ mode all extensions should work at least as in v2.0.
    auto CLVer = LO.OpenCLCPlusPlus ? 200 : LO.OpenCLVersion;
    auto I = OptMap.find(Ext)->getValue();
    return I.Supported && I.Avail <= CLVer;
  }

  // Is supported (optional) OpenCL core features for OpenCL version \p CLVer.
  // For supported extension, return false.
  bool isSupportedCore(llvm::StringRef Ext, const LangOptions &LO) const {
    // In C++ mode all extensions should work at least as in v2.0.
    auto CLVer = LO.OpenCLCPlusPlus ? 200 : LO.OpenCLVersion;
    auto I = OptMap.find(Ext)->getValue();
    return I.Supported && I.Avail <= CLVer && I.Core != ~0U && CLVer >= I.Core;
  }

  // Is supported OpenCL extension for OpenCL version \p CLVer.
  // For supported (optional) core feature, return false.
  bool isSupportedExtension(llvm::StringRef Ext, const LangOptions &LO) const {
    // In C++ mode all extensions should work at least as in v2.0.
    auto CLVer = LO.OpenCLCPlusPlus ? 200 : LO.OpenCLVersion;
    auto I = OptMap.find(Ext)->getValue();
    return I.Supported && I.Avail <= CLVer && (I.Core == ~0U || CLVer < I.Core);
  }

  void enable(llvm::StringRef Ext, bool V = true) {
    OptMap[Ext].Enabled = V;
  }

  /// Enable or disable support for OpenCL extensions
  /// \param Ext name of the extension optionally prefixed with
  ///        '+' or '-'
  /// \param V used when \p Ext is not prefixed by '+' or '-'
  void support(llvm::StringRef Ext, bool V = true) {
    assert(!Ext.empty() && "Extension is empty.");

    switch (Ext[0]) {
    case '+':
      V = true;
      Ext = Ext.drop_front();
      break;
    case '-':
      V = false;
      Ext = Ext.drop_front();
      break;
    }

    if (Ext.equals("all")) {
      supportAll(V);
      return;
    }
    OptMap[Ext].Supported = V;
  }

  OpenCLOptions(){
#define OPENCLEXT_INTERNAL(Ext, AvailVer, CoreVer) \
    OptMap[#Ext].Avail = AvailVer; \
    OptMap[#Ext].Core = CoreVer;
#include "clang/Basic/OpenCLExtensions.def"
  }

  void addSupport(const OpenCLOptions &Opts) {
    for (auto &I:Opts.OptMap)
      if (I.second.Supported)
        OptMap[I.getKey()].Supported = true;
  }

  void copy(const OpenCLOptions &Opts) {
    OptMap = Opts.OptMap;
  }

  // Turn on or off support of all options.
  void supportAll(bool On = true) {
    for (llvm::StringMap<Info>::iterator I = OptMap.begin(),
         E = OptMap.end(); I != E; ++I)
      I->second.Supported = On;
  }

  void disableAll() {
    for (llvm::StringMap<Info>::iterator I = OptMap.begin(),
         E = OptMap.end(); I != E; ++I)
      I->second.Enabled = false;
  }

  void enableSupportedCore(LangOptions LO) {
    for (llvm::StringMap<Info>::iterator I = OptMap.begin(), E = OptMap.end();
         I != E; ++I)
      if (isSupportedCore(I->getKey(), LO))
        I->second.Enabled = true;
  }

  friend class ASTWriter;
  friend class ASTReader;
};

} // end namespace clang

#endif
