//===- BuiltinOps.td - Builtin operation definitions -------*- 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 set of builtin MLIR operations, or the set of operations
// necessary for the validity of and defining the IR.
//
//===----------------------------------------------------------------------===//

#ifndef BUILTIN_OPS
#define BUILTIN_OPS

include "mlir/IR/BuiltinDialect.td"
include "mlir/IR/RegionKindInterface.td"
include "mlir/IR/SymbolInterfaces.td"
include "mlir/Interfaces/CallInterfaces.td"
include "mlir/Interfaces/CastInterfaces.td"
include "mlir/Interfaces/DataLayoutInterfaces.td"
include "mlir/Interfaces/SideEffectInterfaces.td"

// Base class for Builtin dialect ops.
class Builtin_Op<string mnemonic, list<OpTrait> traits = []> :
    Op<Builtin_Dialect, mnemonic, traits>;

//===----------------------------------------------------------------------===//
// FuncOp
//===----------------------------------------------------------------------===//

def FuncOp : Builtin_Op<"func", [
  AffineScope, AutomaticAllocationScope, CallableOpInterface, FunctionLike,
  IsolatedFromAbove, Symbol
]> {
  let summary = "An operation with a name containing a single `SSACFG` region";
  let description = [{
    Operations within the function cannot implicitly capture values defined
    outside of the function, i.e. Functions are `IsolatedFromAbove`. All
    external references must use function arguments or attributes that establish
    a symbolic connection (e.g. symbols referenced by name via a string
    attribute like SymbolRefAttr). An external function declaration (used when
    referring to a function declared in some other module) has no body. While
    the MLIR textual form provides a nice inline syntax for function arguments,
    they are internally represented as “block arguments” to the first block in
    the region.

    Only dialect attribute names may be specified in the attribute dictionaries
    for function arguments, results, or the function itself.

    Example:

    ```mlir
    // External function definitions.
    func @abort()
    func @scribble(i32, i64, memref<? x 128 x f32, #layout_map0>) -> f64

    // A function that returns its argument twice:
    func @count(%x: i64) -> (i64, i64)
      attributes {fruit: "banana"} {
      return %x, %x: i64, i64
    }

    // A function with an argument attribute
    func @example_fn_arg(%x: i32 {swift.self = unit})

    // A function with a result attribute
    func @example_fn_result() -> (f64 {dialectName.attrName = 0 : i64})

    // A function with an attribute
    func @example_fn_attr() attributes {dialectName.attrName = false}
    ```
  }];

  let arguments = (ins SymbolNameAttr:$sym_name,
                       TypeAttr:$type,
                       OptionalAttr<StrAttr>:$sym_visibility);
  let regions = (region AnyRegion:$body);

  let builders = [OpBuilder<(ins
    "StringRef":$name, "FunctionType":$type,
    CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs,
    CArg<"ArrayRef<DictionaryAttr>", "{}">:$argAttrs)
  >];
  let extraClassDeclaration = [{
    static FuncOp create(Location location, StringRef name, FunctionType type,
                         ArrayRef<NamedAttribute> attrs = {});
    static FuncOp create(Location location, StringRef name, FunctionType type,
                         Operation::dialect_attr_range attrs);
    static FuncOp create(Location location, StringRef name, FunctionType type,
                         ArrayRef<NamedAttribute> attrs,
                         ArrayRef<DictionaryAttr> argAttrs);

    /// Create a deep copy of this function and all of its blocks, remapping any
    /// operands that use values outside of the function using the map that is
    /// provided (leaving them alone if no entry is present). If the mapper
    /// contains entries for function arguments, these arguments are not
    /// included in the new function. Replaces references to cloned sub-values
    /// with the corresponding value that is copied, and adds those mappings to
    /// the mapper.
    FuncOp clone(BlockAndValueMapping &mapper);
    FuncOp clone();

    /// Clone the internal blocks and attributes from this function into dest.
    /// Any cloned blocks are appended to the back of dest. This function
    /// asserts that the attributes of the current function and dest are
    /// compatible.
    void cloneInto(FuncOp dest, BlockAndValueMapping &mapper);

    //===------------------------------------------------------------------===//
    // CallableOpInterface
    //===------------------------------------------------------------------===//

    /// Returns the region on the current operation that is callable. This may
    /// return null in the case of an external callable object, e.g. an external
    /// function.
    Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }

    /// Returns the results types that the callable region produces when
    /// executed.
    ArrayRef<Type> getCallableResults() { return getType().getResults(); }

    //===------------------------------------------------------------------===//
    // SymbolOpInterface Methods
    //===------------------------------------------------------------------===//

    bool isDeclaration() { return isExternal(); }

  private:
    // This trait needs access to the hooks defined below.
    friend class OpTrait::FunctionLike<FuncOp>;

    /// Returns the number of arguments. This is a hook for
    /// OpTrait::FunctionLike.
    unsigned getNumFuncArguments() { return getType().getInputs().size(); }

    /// Returns the number of results. This is a hook for OpTrait::FunctionLike.
    unsigned getNumFuncResults() { return getType().getResults().size(); }

    /// Hook for OpTrait::FunctionLike, called after verifying that the 'type'
    /// attribute is present and checks if it holds a function type. Ensures
    /// getType, getNumFuncArguments, and getNumFuncResults can be called
    /// safely.
    LogicalResult verifyType() {
      auto type = getTypeAttr().getValue();
      if (!type.isa<FunctionType>())
        return emitOpError("requires '" + getTypeAttrName() +
                           "' attribute of function type");
      return success();
    }
  }];
  let parser = [{ return ::parseFuncOp(parser, result); }];
  let printer = [{ return ::print(*this, p); }];
  let verifier = [{ return ::verify(*this); }];
}

//===----------------------------------------------------------------------===//
// ModuleOp
//===----------------------------------------------------------------------===//

def ModuleOp : Builtin_Op<"module", [
  AffineScope, IsolatedFromAbove, NoRegionArguments, SymbolTable, Symbol] 
  # GraphRegionNoTerminator.traits> {
  let summary = "A top level container operation";
  let description = [{
    A `module` represents a top-level container operation. It contains a single
    [graph region](../LangRef.md#control-flow-and-ssacfg-regions) containing a single block
    which can contain any operations and does not have a terminator. Operations
    within this region cannot implicitly capture values defined outside the module,
    i.e. Modules are [IsolatedFromAbove](../Traits.md#isolatedfromabove). Modules have
    an optional [symbol name](../SymbolsAndSymbolTables.md) which can be used to refer
    to them in operations.

    Example:

    ```mlir
    module {
      func @foo()
    }
    ```
  }];

  let arguments = (ins OptionalAttr<SymbolNameAttr>:$sym_name,
                       OptionalAttr<StrAttr>:$sym_visibility);
  let regions = (region SizedRegion<1>:$body);

  let assemblyFormat = "($sym_name^)? attr-dict-with-keyword $body";
  let builders = [OpBuilder<(ins CArg<"Optional<StringRef>", "{}">:$name)>];
  let extraClassDeclaration = [{
    /// Construct a module from the given location with an optional name.
    static ModuleOp create(Location loc, Optional<StringRef> name = llvm::None);

    /// Return the name of this module if present.
    Optional<StringRef> getName() { return sym_name(); }

    //===------------------------------------------------------------------===//
    // SymbolOpInterface Methods
    //===------------------------------------------------------------------===//

    /// A ModuleOp may optionally define a symbol.
    bool isOptionalSymbol() { return true; }

    //===------------------------------------------------------------------===//
    // DataLayoutOpInterface Methods
    //===------------------------------------------------------------------===//

    DataLayoutSpecInterface getDataLayoutSpec();
  }];
  let verifier = [{ return ::verify(*this); }];

  // We need to ensure the block inside the region is properly terminated;
  // the auto-generated builders do not guarantee that.
  let skipDefaultBuilders = 1;
}

//===----------------------------------------------------------------------===//
// UnrealizedConversionCastOp
//===----------------------------------------------------------------------===//

def UnrealizedConversionCastOp : Builtin_Op<"unrealized_conversion_cast", [
    DeclareOpInterfaceMethods<CastOpInterface>, NoSideEffect
  ]> {
  let summary = "An unrealized conversion from one set of types to another";
  let description = [{
    An `unrealized_conversion_cast` operation represents an unrealized
    conversion from one set of types to another, that is used to enable the
    inter-mixing of different type systems. This operation should not be
    attributed any special representational or execution semantics, and is
    generally only intended to be used to satisfy the temporary intermixing of
    type systems during the conversion of one type system to another.

    This operation may produce results of arity 1-N, and accept as input
    operands of arity 0-N.

    Example:

    ```mlir
    // An unrealized 0-1 conversion. These types of conversions are useful in
    // cases where a type is removed from the type system, but not all uses have
    // been converted. For example, imagine we have a tuple type that is
    // expanded to its element types. If only some uses of an empty tuple type
    // instance are converted we still need an instance of the tuple type, but
    // have no inputs to the unrealized conversion.
    %result = unrealized_conversion_cast to !bar.tuple_type<>

    // An unrealized 1-1 conversion.
    %result1 = unrealized_conversion_cast %operand : !foo.type to !bar.lowered_type

    // An unrealized 1-N conversion.
    %results2:2 = unrealized_conversion_cast %tuple_operand : !foo.tuple_type<!foo.type, !foo.type> to !foo.type, !foo.type

    // An unrealized N-1 conversion.
    %result3 = unrealized_conversion_cast %operand, %operand : !foo.type, !foo.type to !bar.tuple_type<!foo.type, !foo.type>
    ```
  }];

  let arguments = (ins Variadic<AnyType>:$inputs);
  let results = (outs Variadic<AnyType>:$outputs);
  let assemblyFormat = [{
    ($inputs^ `:` type($inputs))? `to` type($outputs) attr-dict
  }];
  let hasFolder = 1;
}

#endif // BUILTIN_OPS
