//===- DataLayoutInterfaces.td - Data layout interfaces ----*- tablegen -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Defines the interfaces for the data layout specification, operations to which
// they can be attached, and types that are subject to data layout.
//
//===----------------------------------------------------------------------===//

#ifndef MLIR_DATALAYOUTINTERFACES
#define MLIR_DATALAYOUTINTERFACES

include "mlir/IR/OpBase.td"

//===----------------------------------------------------------------------===//
// Attribute interfaces
//===----------------------------------------------------------------------===//

def DataLayoutEntryInterface : AttrInterface<"DataLayoutEntryInterface"> {
  let cppNamespace = "::mlir";

  let description = [{
    Attribute interface describing an entry in a data layout specification.

    A data layout specification entry is a key-value pair. Its key is either a
    type, when the entry is related to a type or a class of types, or an
    identifier, when it is not. `DataLayoutEntryKey` is an alias allowing one
    to use both key types. Its value is an arbitrary attribute that is
    interpreted either by the type for type keys or by the dialect containing
    the identifier for identifier keys. The interface provides a hook that
    can be used by specific implementations to delegate the verification of
    attribute fitness for a particular key to the relevant type or dialect.
  }];

  let methods = [
    InterfaceMethod<
      /*description=*/"Returns the key of the this layout entry.",
      /*retTy=*/"::mlir::DataLayoutEntryKey",
      /*methodName=*/"getKey",
      /*args=*/(ins)
    >,
    InterfaceMethod<
      /*description=*/"Returns the value of this layout entry.",
      /*retTy=*/"::mlir::Attribute",
      /*methodName=*/"getValue",
      /*args=*/(ins)
    >,
    InterfaceMethod<
      /*description=*/"Checks that the entry is well-formed, reports errors "
                      "at the provided location.",
      /*retTy=*/"::mlir::LogicalResult",
      /*methodName=*/"verifyEntry",
      /*args=*/(ins "::mlir::Location":$loc),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{ return ::mlir::success(); }]
    >
  ];

  let extraClassDeclaration = [{
    /// Returns `true` if the key of this entry is a type.
    bool isTypeEntry() {
      return getKey().is<::mlir::Type>();
    }
  }];
}

def DataLayoutSpecInterface : AttrInterface<"DataLayoutSpecInterface"> {
  let cppNamespace = "::mlir";

  let description = [{
    Attribute interface describing a data layout specification.

    A data layout specification is seen as a sequence of entries, each of which
    is an attribute implementing the data layout entry interface. It assumes
    a contiguous underlying storage for entries. The interface provides a hook
    for implementations to verify the well-formedness of the specification,
    with a default implementation that verifies the absence of entries with
    duplicate keys and the well-formedness of each individual entry before
    dispatching to the type or dialect the entry is associated with.

    Data layout specifications may need to be combined in case they appear on
    nested operations subject to layout, or to ensure the validity of layout
    modification. Concrete specification attributes must implement the
    corresponding hook.
  }];
  // The underlying storage being contiguous may be revised in the future, but
  // care must be taken to avoid materializing or copying the entire list of
  // entries.

  let methods = [
    InterfaceMethod<
      /*description=*/"Combines the current layout with the given list of "
                      "layouts, provided from the outermost (oldest) to the "
                      "innermost (newest). Returns null on failure.",
      /*retTy=*/"::mlir::DataLayoutSpecInterface",
      /*methodName=*/"combineWith",
      /*args=*/(ins "::llvm::ArrayRef<::mlir::DataLayoutSpecInterface>":$specs)
    >,
    InterfaceMethod<
      /*description=*/"Returns the list of layout entries.",
      /*retTy=*/"::mlir::DataLayoutEntryListRef",
      /*methodName=*/"getEntries",
      /*args=*/(ins)
    >,
    // Implementations may override this if they have an efficient lookup
    // mechanism.
    InterfaceMethod<
      /*description=*/"Returns a copy of the entries related to a specific "
                      "type class regardles of type parameters. ",
      /*retTy=*/"::mlir::DataLayoutEntryList",
      /*methodName=*/"getSpecForType",
      /*args=*/(ins "::mlir::TypeID":$type),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{
        return ::mlir::detail::filterEntriesForType($_attr.getEntries(), type);
      }]
    >,
    // Implementations may override this if they have an efficient lookup
    // mechanism.
    InterfaceMethod<
      /*description=*/"Returns the entry related to the given identifier, if "
                      "present.",
      /*retTy=*/"::mlir::DataLayoutEntryInterface",
      /*methodName=*/"getSpecForIdentifier",
      /*args=*/(ins "::mlir::StringAttr":$identifier),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{
        return ::mlir::detail::filterEntryForIdentifier($_attr.getEntries(),
                                                        identifier);
      }]
    >,
    InterfaceMethod<
      /*description=*/"Verifies the validity of the specification and reports "
                      "any errors at the given location.",
      /*retTy=*/"::mlir::LogicalResult",
      /*methodName=*/"verifySpec",
      /*args=*/(ins "::mlir::Location":$loc),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{
        return ::mlir::detail::verifyDataLayoutSpec($_attr, loc);
      }]
    >,
  ];

  let extraClassDeclaration = [{
    /// Returns a copy of the entries related to the type specified as template
    /// parameter.
    template <typename Ty>
    DataLayoutEntryList getSpecForType() {
      return getSpecForType(TypeID::get<Ty>());
    }

    /// Populates the given maps with lists of entries grouped by the type or
    /// identifier they are associated with. Users are not expected to call this
    /// method directly.
    void bucketEntriesByType(
        ::llvm::DenseMap<::mlir::TypeID, ::mlir::DataLayoutEntryList> &types,
        ::llvm::DenseMap<::mlir::StringAttr,
                         ::mlir::DataLayoutEntryInterface> &ids);
  }];
}

//===----------------------------------------------------------------------===//
// Operation interface
//===----------------------------------------------------------------------===//

def DataLayoutOpInterface : OpInterface<"DataLayoutOpInterface"> {
  let cppNamespace = "::mlir";

  let description = [{
    Interface for operations that can have a data layout specification attached.

    The `DataLayout` object, which can be used for data layout queries, can be
    constructed for such operations. The absence of a data layout specification
    must be handled without failing.

    Concrete operations must implement the hook returning the data layout
    specification. They may optionally override the methods used in data layout
    queries, default implementations of which provide predefined answers for
    built-in types and dispatch to the type interface for all other types. These
    methods must be idempotent, that is return the same result on repeated
    queries with the same parameters. They are declared static and therefore
    have no access to the operation or its attributes. Instead, they receive a
    list of data layout entries relevant to the request. The entries are known
    to have passed the spec and entry verifier.
  }];

  let methods = [
    InterfaceMethod<
      /*description=*/"Returns the data layout specification for this op, or "
                      "null if it does not exist.",
      /*retTy=*/"::mlir::DataLayoutSpecInterface",
      /*methodName=*/"getDataLayoutSpec",
      /*args=*/(ins)
    >,
    StaticInterfaceMethod<
      /*description=*/"Returns the size of the given type computed using the "
                      "relevant entries. The data layout object can be used "
                      "for recursive queries.",
      /*retTy=*/"unsigned",
      /*methodName=*/"getTypeSize",
      /*args=*/(ins "::mlir::Type":$type,
                    "const ::mlir::DataLayout &":$dataLayout,
                    "::mlir::DataLayoutEntryListRef":$params),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{
        unsigned bits = ConcreteOp::getTypeSizeInBits(type, dataLayout, params);
        return ::llvm::divideCeil(bits, 8);
      }]
    >,
    StaticInterfaceMethod<
      /*description=*/"Returns the size of the given type in bits computed "
                      "using the relevant entries. The data layout object can "
                      "be used for recursive queries.",
      /*retTy=*/"unsigned",
      /*methodName=*/"getTypeSizeInBits",
      /*args=*/(ins "::mlir::Type":$type,
                    "const ::mlir::DataLayout &":$dataLayout,
                    "::mlir::DataLayoutEntryListRef":$params),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{
        return ::mlir::detail::getDefaultTypeSizeInBits(type, dataLayout,
                                                        params);
      }]
    >,
    StaticInterfaceMethod<
      /*description=*/"Returns the alignment required by the ABI for the given "
                      "type computed using the relevant entries. The data "
                      "layout object can be used for recursive queries.",
      /*retTy=*/"unsigned",
      /*methodName=*/"getTypeABIAlignment",
      /*args=*/(ins "::mlir::Type":$type,
                    "const ::mlir::DataLayout &":$dataLayout,
                    "::mlir::DataLayoutEntryListRef":$params),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{
        return ::mlir::detail::getDefaultABIAlignment(type, dataLayout, params);
      }]
    >,
    StaticInterfaceMethod<
      /*description=*/"Returns the alignment preferred by the given type "
                      "computed using the relevant entries. The data layout"
                      "object can be used for recursive queries.",
      /*retTy=*/"unsigned",
      /*methodName=*/"getTypePreferredAlignment",
      /*args=*/(ins "::mlir::Type":$type,
                    "const ::mlir::DataLayout &":$dataLayout,
                    "::mlir::DataLayoutEntryListRef":$params),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{
        return ::mlir::detail::getDefaultPreferredAlignment(type, dataLayout,
                                                            params);
      }]
    >,
  ];

  let verify = [{ return ::mlir::detail::verifyDataLayoutOp($_op); }];
}

//===----------------------------------------------------------------------===//
// Type interface
//===----------------------------------------------------------------------===//

def DataLayoutTypeInterface : TypeInterface<"DataLayoutTypeInterface"> {
  let cppNamespace = "::mlir";

  let description = [{
    Interface for types subject to data layout.

    Types willing to be supported by the data layout subsystem should implement
    this interface by providing implementations of functions querying their
    size, required and preferred alignment. Each of these functions accepts as
    arguments a data layout object that can be used to perform recursive queries
    in the same scope, and a list of data layout entries relevant to this type.
    Specifically, the entries are those that have as key _any instance_ of the
    same type class as the current type. For example, if IntegerType had
    implemented this interface, it would have received the entries with keys i1,
    i2, i8, etc. regardless of the bitwidth of this type. This mechanism allows
    types to "interpolate" the results in a type-specific way instead of listing
    all possible types in the specification.

    The list of entries may be empty, in which case the type must provide a
    reasonable default value. The entries in the list are known to have passed
    the spec and the entry verifiers, as well as the type-specified verifier if
    provided.

    In case of nested layout specs or spec changes, the type can override a hook
    indicating whether the outer (old) and the inner (new) spec are compatible.
  }];

  let methods = [
    InterfaceMethod<
      /*description=*/"Returns the size of this type in bytes.",
      /*retTy=*/"unsigned",
      /*methodName=*/"getTypeSize",
      /*args=*/(ins "const ::mlir::DataLayout &":$dataLayout,
                    "::mlir::DataLayoutEntryListRef":$params),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{
        unsigned bits = $_type.getTypeSizeInBits(dataLayout, params);
        return ::llvm::divideCeil(bits, 8);
      }]
    >,
    InterfaceMethod<
      /*description=*/"Returns the size of this type in bits.",
      /*retTy=*/"unsigned",
      /*methodName=*/"getTypeSizeInBits",
      /*args=*/(ins "const ::mlir::DataLayout &":$dataLayout,
                    "::mlir::DataLayoutEntryListRef":$params)
    >,
    InterfaceMethod<
      /*description=*/"Returns the ABI-required alignment for this type, "
                      "in bytes",
      /*retTy=*/"unsigned",
      /*methodName=*/"getABIAlignment",
      /*args=*/(ins "const ::mlir::DataLayout &":$dataLayout,
                    "::mlir::DataLayoutEntryListRef":$params)
    >,
    InterfaceMethod<
      /*description=*/"Returns the preferred alignment for this type, "
                      "in bytes.",
      /*retTy=*/"unsigned",
      /*methodName=*/"getPreferredAlignment",
      /*args=*/(ins "const ::mlir::DataLayout &":$dataLayout,
                    "::mlir::DataLayoutEntryListRef":$params)
    >,
    InterfaceMethod<
      /*desc=*/"Returns true if the two lists of entries are compatible, that "
               "is, that `newLayout` spec entries can be nested in an op with "
               "`oldLayout` spec entries.",
      /*retTy=*/"bool",
      /*methodName=*/"areCompatible",
      /*args=*/(ins "::mlir::DataLayoutEntryListRef":$oldLayout,
                    "::mlir::DataLayoutEntryListRef":$newLayout),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{ return true; }]
    >,
    InterfaceMethod<
      /*description=*/"Verifies that the given list of entries is valid for "
                      "this type.",
      /*retTy=*/"::mlir::LogicalResult",
      /*methodName=*/"verifyEntries",
      /*args=*/(ins "::mlir::DataLayoutEntryListRef":$entries,
                    "::mlir::Location":$loc),
      /*methodBody=*/"",
      /*defaultImplementation=*/[{ return ::mlir::success(); }]
    >,
  ];
}

#endif // MLIR_DATALAYOUTINTERFACES
