// Copyright 2023 The IREE Authors
//
// Licensed 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 "compiler/plugins/target/MetalSPIRV/MSLToMetalLib.h"

#include <stdlib.h>

#include "llvm/ADT/Twine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/Support/LogicalResult.h"

#define DEBUG_TYPE "iree-msl-to-metal-lib"

namespace mlir::iree_compiler::IREE::HAL {

/// Returns the command to compile the given MSL source file into Metal library.
static std::string getMetalCompileCommand(MetalTargetPlatform platform,
                                          llvm::StringRef mslFile,
                                          llvm::StringRef libFile) {
  const char *sdk = "";
  switch (platform) {
  case MetalTargetPlatform::macOS:
    sdk = "macosx";
    break;
  case MetalTargetPlatform::iOS:
    sdk = "iphoneos";
    break;
  case MetalTargetPlatform::iOSSimulator:
    sdk = "iphonesimulator";
    break;
  }

  // Metal shader offline compilation involves two steps:
  // 1. Compile the MSL source code into an Apple IR (AIR) file,
  // 2. Compile the AIR file into a Metal library.
  return llvm::Twine("xcrun -sdk ")
      .concat(sdk)
      .concat(" metal -c ")
      .concat(mslFile)
      .concat(" -o - | xcrun -sdk ")
      .concat(sdk)
      .concat(" metallib - -o ")
      .concat(libFile)
      .str();
}

/// Returns the given command via system shell.
static LogicalResult runSystemCommand(llvm::StringRef command) {
  LLVM_DEBUG(llvm::dbgs() << "Running system command: '" << command << "'\n");
  int exitCode = system(command.data());
  if (exitCode == 0) {
    return success();
  }
  llvm::errs() << "Failed to run system command '" << command
               << "' with error code: " << exitCode << "\n";
  return failure();
}

std::unique_ptr<llvm::MemoryBuffer>
compileMSLToMetalLib(MetalTargetPlatform targetPlatform,
                     llvm::StringRef mslCode, llvm::StringRef entryPoint) {
  llvm::SmallString<32> mslFile, airFile, libFile;
  int mslFd = 0;
  llvm::sys::fs::createTemporaryFile(entryPoint, "metal", mslFd, mslFile);
  llvm::sys::fs::createTemporaryFile(entryPoint, "metallib", libFile);
  llvm::FileRemover mslRemover(mslFile.c_str());
  llvm::FileRemover libRemover(libFile.c_str());

  { // Write input MSL code to the temporary file.
    llvm::raw_fd_ostream inputStream(mslFd, /*shouldClose=*/true);
    inputStream << mslCode << "\n";
  }

  std::string command =
      getMetalCompileCommand(targetPlatform, mslFile, libFile);
  if (failed(runSystemCommand(command))) {
    return nullptr;
  }

  auto fileOrErr =
      llvm::MemoryBuffer::getFileOrSTDIN(libFile, /*isText=*/false);
  if (std::error_code error = fileOrErr.getError()) {
    llvm::errs() << "Failed to open generated metallib file '" << libFile
                 << "' with error: " << error.message();
    return nullptr;
  }

  return std::move(*fileOrErr);
}

} // namespace mlir::iree_compiler::IREE::HAL
