//===-- objcgen.cpp -------------------------------------------------------===//
//
//                         LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
// Support limited to Objective-C on Darwin (OS X, iOS, tvOS, watchOS)
//
//===----------------------------------------------------------------------===//

#include "gen/objcgen.h"

#include "dmd/mtype.h"
#include "dmd/objc.h"
#include "gen/irstate.h"

namespace {
enum ABI { none = 0, fragile = 1, nonFragile = 2 };
ABI abi = nonFragile;
}

bool objc_isSupported(const llvm::Triple &triple) {
  if (triple.isOSDarwin()) {
    // Objective-C only supported on Darwin at this time
    switch (triple.getArch()) {
    case llvm::Triple::aarch64: // arm64 iOS, tvOS
    case llvm::Triple::arm:     // armv6 iOS
    case llvm::Triple::thumb:   // thumbv7 iOS, watchOS
    case llvm::Triple::x86_64:  // OSX, iOS, tvOS sim
      abi = nonFragile;
      return true;
    case llvm::Triple::x86: // OSX, iOS, watchOS sim
      abi = fragile;
      return true;
    default:
      break;
    }
  }
  return false;
}

LLGlobalVariable *ObjCState::getCStringVar(const char *symbol,
                                           const llvm::StringRef &str,
                                           const char *section) {
  auto init = llvm::ConstantDataArray::getString(module.getContext(), str);
  auto var = new LLGlobalVariable(module, init->getType(), false,
                                  LLGlobalValue::PrivateLinkage, init, symbol);
  var->setSection(section);
  return var;
}

LLGlobalVariable *ObjCState::getMethVarName(const llvm::StringRef &name) {
  auto it = methVarNameMap.find(name);
  if (it != methVarNameMap.end()) {
    return it->second;
  }

  auto var = getCStringVar("OBJC_METH_VAR_NAME_", name,
                           abi == nonFragile
                               ? "__TEXT,__objc_methname,cstring_literals"
                               : "__TEXT,__cstring,cstring_literals");
  methVarNameMap[name] = var;
  retain(var);
  return var;
}

LLGlobalVariable *ObjCState::getMethVarRef(const ObjcSelector &sel) {
  llvm::StringRef s(sel.stringvalue, sel.stringlen);
  auto it = methVarRefMap.find(s);
  if (it != methVarRefMap.end()) {
    return it->second;
  }

  auto gvar = getMethVarName(s);
  auto selref = new LLGlobalVariable(
      module, gvar->getType(),
      false, // prevent const elimination optimization
      LLGlobalValue::PrivateLinkage, gvar, "OBJC_SELECTOR_REFERENCES_", nullptr,
      LLGlobalVariable::NotThreadLocal, 0,
      true); // externally initialized
  selref->setSection(
      abi == nonFragile
          ? "__DATA,__objc_selrefs,literal_pointers,no_dead_strip"
          : "__OBJC,__message_refs,literal_pointers,no_dead_strip");

  // Save for later lookup and prevent optimizer elimination
  methVarRefMap[s] = selref;
  retain(selref);

  return selref;
}

void ObjCState::retain(LLConstant *sym) {
  retainedSymbols.push_back(DtoBitCast(sym, getVoidPtrType()));
}

void ObjCState::finalize() {
  if (!retainedSymbols.empty()) {
    genImageInfo();
    // add in references so optimizer won't remove symbols.
    retainSymbols();
  }
}

void ObjCState::genImageInfo() {
  // Use LLVM to generate image info
  const char *section =
      (abi == nonFragile ? "__DATA,__objc_imageinfo,regular,no_dead_strip"
                         : "__OBJC,__image_info");
  module.addModuleFlag(llvm::Module::Error, "Objective-C Version",
                       abi); //  unused?
  module.addModuleFlag(llvm::Module::Error, "Objective-C Image Info Version",
                       0u); // version
  module.addModuleFlag(llvm::Module::Error, "Objective-C Image Info Section",
                       llvm::MDString::get(module.getContext(), section));
  module.addModuleFlag(llvm::Module::Override, "Objective-C Garbage Collection",
                       0u); // flags
}

void ObjCState::retainSymbols() {
  // put all objc symbols in the llvm.compiler.used array so optimizer won't
  // remove.
  auto arrayType = LLArrayType::get(retainedSymbols.front()->getType(),
                                    retainedSymbols.size());
  auto usedArray = LLConstantArray::get(arrayType, retainedSymbols);
  auto var = new LLGlobalVariable(module, arrayType, false,
                                  LLGlobalValue::AppendingLinkage, usedArray,
                                  "llvm.compiler.used");
  var->setSection("llvm.metadata");
}
