llvm/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td

//===- SCFTransformOps.td - SCF (loop) transformation ops --*- 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
//
//===----------------------------------------------------------------------===//

#ifndef SCF_TRANSFORM_OPS
#define SCF_TRANSFORM_OPS

include "mlir/Dialect/Transform/IR/TransformDialect.td"
include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.td"
include "mlir/Dialect/Transform/IR/TransformTypes.td"
include "mlir/Interfaces/SideEffectInterfaces.td"
include "mlir/IR/OpBase.td"

def ApplyForLoopCanonicalizationPatternsOp : Op<Transform_Dialect,
    "apply_patterns.scf.for_loop_canonicalization",
    [DeclareOpInterfaceMethods<PatternDescriptorOpInterface>]> {
  let description = [{
    Collects patterns for canonicalizing operations inside SCF loop bodies.
    At the moment, only affine.min/max computations with iteration variables,
    loop bounds and loop steps are canonicalized.
  }];

  let assemblyFormat = "attr-dict";
}

def ApplySCFStructuralConversionPatternsOp : Op<Transform_Dialect,
    "apply_conversion_patterns.scf.structural_conversions",
    [DeclareOpInterfaceMethods<ConversionPatternDescriptorOpInterface,
      ["populateConversionTargetRules"]>]> {
  let description = [{
    Collects patterns for performing structural conversions of SCF operations.
  }];

  let assemblyFormat = "attr-dict";
}

def ApplySCFToControlFlowPatternsOp : Op<Transform_Dialect,
    "apply_conversion_patterns.scf.scf_to_control_flow",
    [DeclareOpInterfaceMethods<ConversionPatternDescriptorOpInterface>]> {
  let description = [{
    Collects patterns that lower structured control flow ops to unstructured
    control flow.
  }];

  let assemblyFormat = "attr-dict";
}

def Transform_ScfForOp : Transform_ConcreteOpType<"scf.for">;

def ForallToForOp : Op<Transform_Dialect, "loop.forall_to_for",
    [FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
     DeclareOpInterfaceMethods<TransformOpInterface>]> {
  let summary = "Converts scf.forall into a nest of scf.for operations";
  let description = [{
    Converts the `scf.forall` operation pointed to by the given handle into a
    set of nested `scf.for` operations. Each new operation corresponds to one
    induction variable of the original "multifor" loop.

    The operand handle must be associated with exactly one payload operation.

    Loops with shared outputs are currently not supported.

    #### Return Modes

    Consumes the operand handle. Produces a silenceable failure if the operand
    is not associated with a single `scf.forall` payload operation.
    Returns as many handles as the given `forall` op has induction variables
    that are associated with the generated `scf.for` loops.
    Produces a silenceable failure if another number of resulting handles is
    requested.
  }];
  let arguments = (ins TransformHandleTypeInterface:$target);
  let results = (outs Variadic<TransformHandleTypeInterface>:$transformed);

  let assemblyFormat = "$target attr-dict `:` functional-type(operands, results)";
}

def ForallToParallelOp : Op<Transform_Dialect, "loop.forall_to_parallel",
    [FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
     DeclareOpInterfaceMethods<TransformOpInterface>]> {
  let summary = "Converts scf.forall into a nest of scf.for operations";
  let description = [{
    Converts the `scf.forall` operation pointed to by the given handle into an
    `scf.parallel` operation.

    The operand handle must be associated with exactly one payload operation.

    Loops with outputs are not supported.

    #### Return Modes

    Consumes the operand handle. Produces a silenceable failure if the operand
    is not associated with a single `scf.forall` payload operation.
    Returns a handle to the new `scf.parallel` operation.
    Produces a silenceable failure if another number of resulting handles is
    requested.
  }];
  let arguments = (ins TransformHandleTypeInterface:$target);
  let results = (outs Variadic<TransformHandleTypeInterface>:$transformed);

  let assemblyFormat = "$target attr-dict `:` functional-type(operands, results)";
}

def LoopOutlineOp : Op<Transform_Dialect, "loop.outline",
    [FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
     DeclareOpInterfaceMethods<TransformOpInterface>]> {
  let summary = "Outlines a loop into a named function";
  let description = [{
    Moves the loop into a separate function with the specified name and replaces
    the loop in the Payload IR with a call to that function. Takes care of
    forwarding values that are used in the loop as function arguments. If the
    operand is associated with more than one loop, each loop will be outlined
    into a separate function. The provided name is used as a _base_ for forming
    actual function names following `SymbolTable` auto-renaming scheme to avoid
    duplicate symbols. Expects that all ops in the Payload IR have a
    `SymbolTable` ancestor (typically true because of the top-level module).

    #### Return Modes

    Returns a handle to the list of outlined functions and a handle to the
    corresponding function call operations in the same order as the operand
    handle.

    Produces a definite failure if outlining failed for any of the targets.
  }];

  // Note that despite the name of the transform operation and related utility
  // functions, the actual implementation does not require the operation to be
  // a loop.
  let arguments = (ins TransformHandleTypeInterface:$target,
                   StrAttr:$func_name);
  let results = (outs TransformHandleTypeInterface:$function,
                      TransformHandleTypeInterface:$call);

  let assemblyFormat =
    "$target attr-dict `:` functional-type(operands, results)";
}

def LoopPeelOp : Op<Transform_Dialect, "loop.peel",
    [FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
     TransformOpInterface, TransformEachOpTrait]> {
  let summary = "Peels the first or last iteration of the loop";
  let description = [{
     Rewrite the given loop with a main loop and a partial (first or last) loop.
     When the `peelFront` option is set as true, the first iteration is peeled off.
     Otherwise, updates the given loop so that its step evenly divides its range and puts
     the remaining iteration into a separate loop or a conditional.

     In the absence of sufficient static information, this op may peel a loop,
     even if the step always divides the range evenly at runtime.

     #### Return modes

     This operation ignores non-scf::ForOp ops and drops them in the return.

     When `peelFront` is true, this operation returns two scf::ForOp Ops, the
     first scf::ForOp corresponds to the first iteration of the loop which can
     be canonicalized away in the following optimization. The second loop Op
     contains the remaining iteration, and the new lower bound is the original
     lower bound plus the number of steps.

     When `peelFront` is not true, this operation returns two scf::ForOp Ops, with the first
     scf::ForOp satisfying: "the loop trip count is divisible by the step".
     The second loop Op contains the remaining iteration. Note that even though the
     Payload IR modification may be performed in-place, this operation consumes
     the operand handle and produces a new one.

     #### Return Modes

     Produces a definite failure if peeling fails.
  }];

  let arguments =
      (ins Transform_ScfForOp:$target,
           DefaultValuedAttr<BoolAttr, "false">:$peel_front,
           DefaultValuedAttr<BoolAttr, "false">:$fail_if_already_divisible);
  let results = (outs TransformHandleTypeInterface:$peeled_loop,
                      TransformHandleTypeInterface:$remainder_loop);

  let assemblyFormat =
    "$target attr-dict `:` functional-type(operands, results)";

  let extraClassDeclaration = [{
    ::mlir::DiagnosedSilenceableFailure applyToOne(
        ::mlir::transform::TransformRewriter &rewriter,
        ::mlir::scf::ForOp target,
        ::mlir::transform::ApplyToEachResultList &results,
        ::mlir::transform::TransformState &state);
  }];
}

def LoopPipelineOp : Op<Transform_Dialect, "loop.pipeline",
    [FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
     TransformOpInterface, TransformEachOpTrait]> {
  let summary = "Applies software pipelining to the loop";
  let description = [{
    Transforms the given loops one by one to achieve software pipelining for
    each of them. That is, performs some amount of reads from memory before the
    loop rather than inside the loop, the same amount of writes into memory
    after the loop, and updates each iteration to read the data for a following
    iteration rather than the current one.

    The amount is specified by the attributes.

    The values read and about to be stored are transferred as loop iteration
    arguments. Currently supports memref and vector transfer operations as
    memory reads/writes.

    #### Return modes

    This operation ignores non-scf::For ops and drops them in the return.
    If all the operations referred to by the `target` PDLOperation pipeline
    properly, the transform succeeds. Otherwise the transform produces a
    silenceable failure.  The return handle points to only the subset of
    successfully produced pipelined loops, which can be empty.
  }];

  let arguments = (ins Transform_ScfForOp:$target,
                   DefaultValuedAttr<I64Attr, "1">:$iteration_interval,
                   DefaultValuedAttr<I64Attr, "10">:$read_latency);
  let results = (outs TransformHandleTypeInterface:$transformed);

  let assemblyFormat =
    "$target attr-dict `:` functional-type(operands, results)";

  let extraClassDeclaration = [{
    ::mlir::DiagnosedSilenceableFailure applyToOne(
        ::mlir::transform::TransformRewriter &rewriter,
        ::mlir::scf::ForOp target,
        ::mlir::transform::ApplyToEachResultList &results,
        ::mlir::transform::TransformState &state);
  }];
}

def LoopPromoteIfOneIterationOp : Op<Transform_Dialect,
    "loop.promote_if_one_iteration", [
        DeclareOpInterfaceMethods<MemoryEffectsOpInterface>,
        TransformOpInterface, TransformEachOpTrait]> {
  let summary = "Promote loop if it has one iteration";
  let description = [{
    Promotes the given target loop op if it has a single iteration. I.e., the
    loop op is removed and only the body remains.

    #### Return modes

    This transform fails if the target is mapped to ops that are loops. Ops are
    considered loops if they implement the `LoopLikeOpInterface`. Otherwise,
    this transform always succeeds. The transform consumes the target handle and
    modifies the payload.
  }];

  let arguments = (ins TransformHandleTypeInterface:$target);
  let results = (outs);
  let assemblyFormat = "$target attr-dict `:` type($target)";

  let extraClassDeclaration = [{
    ::mlir::DiagnosedSilenceableFailure applyToOne(
        ::mlir::transform::TransformRewriter &rewriter,
        ::mlir::LoopLikeOpInterface target,
        ::mlir::transform::ApplyToEachResultList &results,
        ::mlir::transform::TransformState &state);
  }];
}

def LoopUnrollOp : Op<Transform_Dialect, "loop.unroll",
    [FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
     TransformOpInterface, TransformEachOpTrait]> {
  let summary = "Unrolls the given loop with the given unroll factor";
  let description = [{
    Unrolls each loop associated with the given handle to have up to the given
    number of loop body copies per iteration. If the unroll factor is larger
    than the loop trip count, the latter is used as the unroll factor instead.

    #### Return modes

    This operation ignores non-`scf.for`, non-`affine.for` ops and drops them
    in the return. If all the operations referred to by the `target` operand
    unroll properly, the transform succeeds. Otherwise the transform produces a
    silenceable failure.

    Does not return handles as the operation may result in the loop being
    removed after a full unrolling.
  }];

  let arguments = (ins TransformHandleTypeInterface:$target,
                       ConfinedAttr<I64Attr, [IntPositive]>:$factor);

  let assemblyFormat = "$target attr-dict `:` type($target)";

  let extraClassDeclaration = [{
    ::mlir::DiagnosedSilenceableFailure applyToOne(
        ::mlir::transform::TransformRewriter &rewriter,
        ::mlir::Operation *target,
        ::mlir::transform::ApplyToEachResultList &results,
        ::mlir::transform::TransformState &state);
  }];
}

def LoopUnrollAndJamOp : Op<Transform_Dialect, "loop.unroll_and_jam",
    [FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
     TransformOpInterface, TransformEachOpTrait]> {
  let summary = "Unrolls and jam the given loop with the given unroll factor";
  let description = [{
    Unrolls & jams each loop associated with the given handle to have up to the given
    number of loop body copies per iteration. If the unroll factor is larger
    than the loop trip count, the latter is used as the unroll factor instead.

    #### Return modes

    This operation ignores non-`scf.for`, non-`affine.for` ops and drops them
    in the return. If all the operations referred to by the `target` operand
    unroll properly, the transform succeeds. Otherwise the transform produces a
    silenceable failure.

    Does not return handles as the operation may result in the loop being
    removed after a full unrolling.
  }];

  let arguments = (ins TransformHandleTypeInterface:$target,
                       ConfinedAttr<I64Attr, [IntPositive]>:$factor);

  let assemblyFormat = "$target attr-dict `:` type($target)";

  let extraClassDeclaration = [{
    ::mlir::DiagnosedSilenceableFailure applyToOne(
        ::mlir::transform::TransformRewriter &rewriter,
        ::mlir::Operation *target,
        ::mlir::transform::ApplyToEachResultList &results,
        ::mlir::transform::TransformState &state);
  }];
}

def LoopCoalesceOp : Op<Transform_Dialect, "loop.coalesce", [
  FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
  TransformOpInterface, TransformEachOpTrait]> {
  let summary = "Coalesces the perfect loop nest enclosed by a given loop";
  let description = [{
    Given a perfect loop nest identified by the outermost loop,
    perform loop coalescing in a bottom-up one-by-one manner.

    #### Return modes

    The return handle points to the coalesced loop if coalescing happens, or
    the given input loop if coalescing does not happen.
  }];
  let arguments = (ins TransformHandleTypeInterface:$target);
  let results = (outs TransformHandleTypeInterface:$transformed);

  let assemblyFormat =
      "$target attr-dict `:` functional-type($target, $transformed)";

  let extraClassDeclaration = [{
    ::mlir::DiagnosedSilenceableFailure applyToOne(
        ::mlir::transform::TransformRewriter &rewriter,
        ::mlir::Operation *target,
        ::mlir::transform::ApplyToEachResultList &results,
        ::mlir::transform::TransformState &state);
  }];
}

def TakeAssumedBranchOp : Op<Transform_Dialect, "scf.take_assumed_branch", [
  DeclareOpInterfaceMethods<MemoryEffectsOpInterface>,
  TransformOpInterface, TransformEachOpTrait]> {
  let description = [{
    Given an scf.if conditional, inject user-defined information that it is
    always safe to execute only the if or else branch.

    This is achieved by just replacing the scf.if by the content of one of its
    branches.

    This is particularly useful for user-controlled rewriting of conditionals
    that exist solely to guard against out-of-bounds behavior.

    At the moment, no assume or assert operation is emitted as it is not always
    desirable. In the future, this may be controlled by a dedicated attribute.

    #### Return modes

    The transform only consumes its operand and does not produce any result.
    The transform definitely fails if `take_else_branch` is specified and the
    `else` region is empty.
  }];
  let arguments = (ins TransformHandleTypeInterface:$target,
                       OptionalAttr<UnitAttr>:$take_else_branch);
  let results = (outs);

  let assemblyFormat = [{
      $target
      (`take_else_branch` $take_else_branch^)?
      attr-dict
       `:` functional-type(operands, results)
  }];

  let extraClassDeclaration = [{
    ::mlir::DiagnosedSilenceableFailure applyToOne(
        ::mlir::transform::TransformRewriter &rewriter,
        ::mlir::scf::IfOp ifOp,
        ::mlir::transform::ApplyToEachResultList &results,
        ::mlir::transform::TransformState &state);
  }];
}

def LoopFuseSiblingOp : Op<Transform_Dialect, "loop.fuse_sibling",
  [FunctionalStyleTransformOpTrait, MemoryEffectsOpInterface,
   DeclareOpInterfaceMethods<TransformOpInterface>]> {
  let summary = "Fuse a loop into another loop, assuming the fusion is legal.";

  let description = [{
    Fuses the `target` loop into the `source` loop assuming they are
    independent of each other. In the fused loop, the arguments, body and
    results of `target` are placed _before_ those of `source`.

    For fusion of two `scf.for` loops, the bounds and step size must match. For
    fusion of two `scf.forall` loops, the bounds and the mapping must match.
    Otherwise a silencable failure is produced.

    The `target` and `source` handles must refer to exactly one operation,
    otherwise a definite failure is produced. It is the responsibility of the
    user to ensure that the `target` and `source` loops are independent of each
    other -- this op will only perform rudimentary legality checks.

    #### Return modes

    This operation consumes the `target` and `source` handles and produces the
    `fused_loop` handle, which points to the fused loop.
  }];

  let arguments = (ins TransformHandleTypeInterface:$target,
                       TransformHandleTypeInterface:$source);
  let results = (outs TransformHandleTypeInterface:$fused_loop);
  let assemblyFormat = "$target `into` $source attr-dict "
                       " `:` functional-type(operands, results)";
}

#endif // SCF_TRANSFORM_OPS