// Copyright 2019 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 "iree/compiler/Dialect/HAL/IR/HALTypes.h"

#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
#include "iree/compiler/Utils/StringUtils.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/IR/Matchers.h"

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

//===----------------------------------------------------------------------===//
// Alignment
//===----------------------------------------------------------------------===//

llvm::MaybeAlign commonAlignment(llvm::MaybeAlign lhs, llvm::MaybeAlign rhs) {
  if (!lhs.has_value() || !rhs.has_value())
    return std::nullopt;
  return llvm::MaybeAlign(
      llvm::MinAlign(lhs.value().value(), rhs.value().value()));
}

// TODO(benvanik): share with align op folder and analysis.
// May need an interface for querying the alignment from ops that can carry it.
std::optional<uint64_t> lookupOffsetOrAlignment(Value value) {
  APInt constantValue;
  if (matchPattern(value, m_ConstantInt(&constantValue))) {
    // Value is constant and we can just treat that as if it were an alignment.
    return constantValue.getZExtValue();
  }

  auto op = value.getDefiningOp();
  if (!op)
    return std::nullopt;
  if (auto alignmentAttr = op->getAttrOfType<IntegerAttr>("stream.alignment")) {
    // The op has an alignment tagged on it we can use directly.
    return alignmentAttr.getValue().getZExtValue();
  }

  // TODO(benvanik): walk other pass-through. These are the most common in our
  // programs today.
  if (auto loadOp = dyn_cast<IREE::HAL::InterfaceConstantLoadOp>(op)) {
    // Push constants have an optional value alignment.
    auto alignment = loadOp.getAlignment();
    if (alignment.has_value()) {
      return alignment.value().getZExtValue();
    }
  } else if (auto castOp = dyn_cast<arith::IndexCastUIOp>(op)) {
    return lookupOffsetOrAlignment(castOp.getOperand());
  }

  // TODO(benvanik): more searching using util.align and other ops.
  return std::nullopt;
}

//===----------------------------------------------------------------------===//
// Object types
//===----------------------------------------------------------------------===//

Value BufferType::inferSizeFromValue(Location loc, Value value,
                                     OpBuilder &builder) const {
  return builder.createOrFold<BufferLengthOp>(loc, builder.getIndexType(),
                                              value);
}

Value BufferViewType::inferSizeFromValue(Location loc, Value value,
                                         OpBuilder &builder) const {
  return builder.createOrFold<BufferLengthOp>(
      loc, builder.getIndexType(),
      builder.createOrFold<BufferViewBufferOp>(
          loc, builder.getType<IREE::HAL::BufferType>(), value));
}

// static
Value DeviceType::resolveAny(Location loc, OpBuilder &builder) {
  Value deviceIndex = builder.create<arith::ConstantIndexOp>(loc, 0);
  return builder.create<IREE::HAL::DevicesGetOp>(
      loc, builder.getType<IREE::HAL::DeviceType>(), deviceIndex);
}

//===----------------------------------------------------------------------===//
// Utilities
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
// Dialect registration
//===----------------------------------------------------------------------===//

#include "iree/compiler/Dialect/HAL/IR/HALOpInterfaces.cpp.inc"
#include "iree/compiler/Dialect/HAL/IR/HALTypeInterfaces.cpp.inc"

void HALDialect::registerTypes() {
  addTypes<AllocatorType, BufferType, BufferViewType, ChannelType,
           CommandBufferType, DeviceType, EventType, ExecutableType, FenceType,
           FileType, SemaphoreType>();
}

Type HALDialect::parseType(DialectAsmParser &parser) const {
  StringRef typeKind;
  if (parser.parseKeyword(&typeKind))
    return {};
  auto type = llvm::StringSwitch<Type>(typeKind)
                  .Case("allocator", AllocatorType::get(getContext()))
                  .Case("buffer", BufferType::get(getContext()))
                  .Case("buffer_view", BufferViewType::get(getContext()))
                  .Case("channel", ChannelType::get(getContext()))
                  .Case("command_buffer", CommandBufferType::get(getContext()))
                  .Case("device", DeviceType::get(getContext()))
                  .Case("event", EventType::get(getContext()))
                  .Case("executable", ExecutableType::get(getContext()))
                  .Case("fence", FenceType::get(getContext()))
                  .Case("file", FileType::get(getContext()))
                  .Case("semaphore", SemaphoreType::get(getContext()))
                  .Default(nullptr);
  if (!type) {
    parser.emitError(parser.getCurrentLocation())
        << "unknown HAL type: " << typeKind;
  }
  return type;
}

void HALDialect::printType(Type type, DialectAsmPrinter &p) const {
  if (llvm::isa<AllocatorType>(type)) {
    p << "allocator";
  } else if (llvm::isa<BufferType>(type)) {
    p << "buffer";
  } else if (llvm::isa<BufferViewType>(type)) {
    p << "buffer_view";
  } else if (llvm::isa<ChannelType>(type)) {
    p << "channel";
  } else if (llvm::isa<CommandBufferType>(type)) {
    p << "command_buffer";
  } else if (llvm::isa<DeviceType>(type)) {
    p << "device";
  } else if (llvm::isa<EventType>(type)) {
    p << "event";
  } else if (llvm::isa<ExecutableType>(type)) {
    p << "executable";
  } else if (llvm::isa<FenceType>(type)) {
    p << "fence";
  } else if (llvm::isa<FileType>(type)) {
    p << "file";
  } else if (llvm::isa<SemaphoreType>(type)) {
    p << "semaphore";
  } else {
    assert(false && "unknown HAL type");
  }
}

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