[flang][fir] Add remaining Ops. Updates to pre-existing Ops.

  - add ops: rebox, insert_on_range, absent, is_present
  - embox, coordinate_of: replace old hand-written parser/pretty-printer with assembly format
  - remove dead floating point ops, since buitlins work for all types
  - update call op
  - update documentation
  - misc. NFC to formatting
  - add op round trip tests

Authors: Eric Schweitz, Jean Perier, Zachary Selk, Kiran Chandramohan, et.al.

Differential Revision: https://reviews.llvm.org/D97500

GitOrigin-RevId: 0b785a46b7eecbfd24394b42814844e02e5fefe6
diff --git a/include/flang/Optimizer/Dialect/FIROps.td b/include/flang/Optimizer/Dialect/FIROps.td
index 6500ab2..5b09559 100644
--- a/include/flang/Optimizer/Dialect/FIROps.td
+++ b/include/flang/Optimizer/Dialect/FIROps.td
@@ -170,6 +170,7 @@
     mlir::Type getAllocatedType();
 
     bool hasLenParams() { return bool{(*this)->getAttr(lenpName())}; }
+    bool hasShapeOperands() { return numShapeOperands() > 0; }
 
     unsigned numLenParams() {
       if (auto val = (*this)->getAttrOfType<mlir::IntegerAttr>(lenpName()))
@@ -985,9 +986,11 @@
   let assemblyFormat = "$resval attr-dict `:` type($resval)";
 }
 
+//===------------------------------------------------------------------------===//
 // Operations on !fir.box<T> type objects
+//===------------------------------------------------------------------------===//
 
-def fir_EmboxOp : fir_Op<"embox", [NoSideEffect]> {
+def fir_EmboxOp : fir_Op<"embox", [NoSideEffect, AttrSizedOperandSegments]> {
   let summary = "boxes a given reference and (optional) dimension information";
 
   let description = [{
@@ -999,83 +1002,105 @@
     ```mlir
       %c1 = constant 1 : index
       %c10 = constant 10 : index
-      %4 = fir.dims(%c1, %c10, %c1) : (index, index, index) -> !fir.dims<1>
       %5 = ... : !fir.ref<!fir.array<10 x i32>>
-      %6 = fir.embox %5, %4 : (!fir.ref<!fir.array<10 x i32>>, !fir.dims<1>) -> !fir.box<!fir.array<10 x i32>>
+      %6 = fir.embox %5 : (!fir.ref<!fir.array<10 x i32>>) -> !fir.box<!fir.array<10 x i32>>
     ```
 
     The descriptor tuple may contain additional implementation-specific
     information through the use of additional attributes.
+    Specifically,
+        - shape: emboxing an array may require shape information (an array's
+          lower bounds and extents may not be known until runtime),
+        - slice: an array section can be described with a slice triple,
+        - lenParams: for emboxing a derived type with LEN type parameters,
+        - accessMap: unused/experimental.
   }];
 
-  let arguments = (ins AnyReferenceLike:$memref, Variadic<AnyEmboxArg>:$args);
+  let arguments = (ins
+    AnyReferenceLike:$memref,
+    Optional<AnyShapeType>:$shape,
+    Optional<fir_SliceType>:$slice,
+    Variadic<AnyIntegerType>:$lenParams,
+    OptionalAttr<AffineMapAttr>:$accessMap
+  );
 
   let results = (outs fir_BoxType);
 
-  let parser = "return parseEmboxOp(parser, result);";
+  let builders = [
+    OpBuilderDAG<(ins "llvm::ArrayRef<mlir::Type>":$resultTypes,
+      "mlir::Value":$memref, CArg<"mlir::Value", "{}">:$shape,
+      CArg<"mlir::Value", "{}">:$slice,
+      CArg<"mlir::ValueRange", "{}">:$lenParams),
+    [{ return build($_builder, $_state, resultTypes, memref, shape, slice,
+                    lenParams, mlir::AffineMapAttr{}); }]>
+  ];
 
-  let printer = [{
-    p << getOperationName() << ' ';
-    p.printOperand(memref());
-    if (hasLenParams()) {
-      p << '(';
-      p.printOperands(getLenParams());
-      p << ')';
-    }
-    if (getNumOperands() == 2) {
-      p << ", ";
-      p.printOperands(dims());
-    } else if (auto map = (*this)->getAttr(layoutName())) {
-      p << " [" << map << ']';
-    }
-    p.printOptionalAttrDict((*this)->getAttrs(), {layoutName(), lenpName()});
-    p << " : ";
-    p.printFunctionalType(getOperation());
+  let assemblyFormat = [{
+    $memref (`(` $shape^ `)`)? (`[` $slice^ `]`)? (`typeparams` $lenParams^)?
+      (`map` $accessMap^)? attr-dict `:` functional-type(operands, results)
   }];
 
-  let verifier = [{
-    if (hasLenParams()) {
-      auto lenParams = numLenParams();
-      auto eleTy = fir::dyn_cast_ptrEleTy(memref().getType());
-      if (!eleTy)
-        return emitOpError("must embox a memory reference type");
-      if (auto rt = eleTy.dyn_cast<fir::RecordType>()) {
-        if (lenParams != rt.getNumLenParams())
-          return emitOpError("number of LEN params does not correspond"
-                             " to the !fir.type type");
-      } else {
-        return emitOpError("LEN parameters require !fir.type type");
-      }
-    }
-    if (dims().size() == 0) {
-      // Ok. If there is no dims and no layout map, then emboxing a scalar.
-      // TODO: Should the type be enforced? It already must agree.
-    } else if (dims().size() == 1) {
-      //auto d = *dims().begin();
-    } else {
-      return emitOpError("embox can only have one !fir.dim argument");
-    }
-    return mlir::success();
-  }];
+  let verifier = [{ return ::verify(*this); }];
 
   let extraClassDeclaration = [{
-    static constexpr llvm::StringRef layoutName() { return "layout_map"; }
-    static constexpr llvm::StringRef lenpName() { return "len_param_count"; }
-    bool hasLenParams() { return bool{(*this)->getAttr(lenpName())}; }
-    unsigned numLenParams() {
-      if (auto x = (*this)->getAttrOfType<mlir::IntegerAttr>(lenpName()))
-        return x.getInt();
-      return 0;
-    }
-    operand_range getLenParams() {
-      return {operand_begin(), operand_begin() + numLenParams()};
-    }
-    operand_range dims() {
-      return {operand_begin() + numLenParams() + 1, operand_end()};
-    }
+    mlir::Value getShape() { return shape(); }
+    mlir::Value getSlice() { return slice(); }
+    bool hasLenParams() { return !lenParams().empty(); }
+    unsigned numLenParams() { return lenParams().size(); }
   }];
 }
 
+def fir_ReboxOp : fir_Op<"rebox", [NoSideEffect, AttrSizedOperandSegments]> {
+  let summary = "create a box given another box and (optional) dimension information";
+
+  let description = [{
+    Create a new boxed reference value from another box. This is meant to be used
+    when the taking a reference to part of a boxed value, or to an entire boxed value with
+    new shape or type information.
+
+    The new extra information can be:
+      - new shape information (new lower bounds, new rank, or new extents.
+        New rank/extents can only be provided if the original fir.box is
+        contiguous in all dimension but maybe the first one). The shape
+        operand must be provided to set new shape information.
+      - new type (only for derived types). It is possible to set the dynamic type
+        of the new box to one of the parent types of the input box dynamic type.
+        Type parameters cannot be changed. This change is reflected in the requested
+        result type of the new box.
+
+    A slice argument can be provided to build a reference to part of a boxed value.
+    In this case, the shape operand must be absent or be a fir.shift that can be
+    used to provide a non default origin for the slice.
+
+    The following example illustrates creating a fir.box for x(10:33:2)
+    where x is described by a fir.box and has non default lower bounds,
+    and then applying a new 2-dimension shape to this fir.box.
+
+    ```mlir
+      %0 = fir.slice %c10, %c33, %c2 : (index, index, index) -> !fir.slice<1>
+      %1 = fir.shift %c0 : (index) -> !fir.shift<1>
+      %2 = fir.rebox %x(%1) [%0] : (!fir.box<!fir.array<?xf32>>, !fir.shift<1>, !fir.slice<1>) -> !fir.box<!fir.array<?xf32>>
+      %3 = fir.shape %c3, %c4 : (index, index) -> !fir.shape<2>
+      %4 = fir.rebox %2(%3) : (!fir.box<!fir.array<?xf32>>, !fir.shape<2>) -> !fir.box<!fir.array<?x?xf32>>
+    ```
+
+  }];
+
+  let arguments = (ins
+    fir_BoxType:$box,
+    Optional<AnyShapeOrShiftType>:$shape,
+    Optional<fir_SliceType>:$slice
+  );
+
+  let results = (outs fir_BoxType);
+
+  let assemblyFormat = [{
+    $box (`(` $shape^ `)`)? (`[` $slice^ `]`)? attr-dict `:` functional-type(operands, results)
+  }];
+
+  let verifier = [{ return ::verify(*this); }];
+}
+
 def fir_EmboxCharOp : fir_Op<"emboxchar", [NoSideEffect]> {
   let summary = "boxes a given CHARACTER reference and its LEN parameter";
 
@@ -1774,58 +1799,28 @@
     array `%h`.
   }];
 
-  let arguments = (ins AnyRefOrBox:$ref, Variadic<AnyCoordinateType>:$coor);
+  let arguments = (ins
+    AnyRefOrBox:$ref,
+    Variadic<AnyCoordinateType>:$coor,
+    TypeAttr:$baseType
+  );
 
   let results = (outs fir_ReferenceType);
 
-  let parser = "return parseCoordinateOp(parser, result);";
+  let parser =  [{ return parseCoordinateCustom(parser, result); }];
+  let printer = [{ ::print(p, *this); }];
+  let verifier = [{ return ::verify(*this); }];
 
-  let printer = [{
-    p << getOperationName() << ' ' << (*this)->getOperands();
-    p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{baseType()});
-    p << " : ";
-    p.printFunctionalType((*this)->getOperandTypes(),
-        (*this)->getResultTypes());
-  }];
-
-  let verifier = [{
-    auto refTy = ref().getType();
-    if (fir::isa_ref_type(refTy)) {
-      auto eleTy = fir::dyn_cast_ptrEleTy(refTy);
-      if (auto arrTy = eleTy.dyn_cast<fir::SequenceType>()) {
-        if (arrTy.hasUnknownShape())
-          return emitOpError("cannot find coordinate in unknown shape");
-        if (arrTy.getConstantRows() < arrTy.getDimension() - 1)
-          return emitOpError("cannot find coordinate with unknown extents");
-      }
-    }
-    // Recovering a LEN type parameter only makes sense from a boxed value
-    for (auto co : coor())
-      if (dyn_cast_or_null<LenParamIndexOp>(co.getDefiningOp())) {
-        if (getNumOperands() != 2)
-          return emitOpError("len_param_index must be last argument");
-        if (!ref().getType().dyn_cast<fir::BoxType>())
-          return emitOpError("len_param_index must be used on box type");
-      }
-    if (auto attr = (*this)->getAttr(CoordinateOp::baseType())) {
-      if (!attr.isa<mlir::TypeAttr>())
-        return emitOpError("improperly constructed");
-    } else {
-      return emitOpError("must have base type");
-    }
-    return mlir::success();
-  }];
-
-  let skipDefaultBuilders = 1;
   let builders = [
-    OpBuilderDAG<(ins "Type":$type, "Value":$ref, "ValueRange":$coor,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>,
-    OpBuilderDAG<(ins "Type":$type, "ValueRange":$operands,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>];
+    OpBuilderDAG<(ins "mlir::Type":$resultType,
+      "mlir::Value":$ref, "mlir::ValueRange":$coor),
+    [{ return build($_builder, $_state, resultType, ref, coor,
+           mlir::TypeAttr::get(ref.getType())); }]>,
+  ];
 
   let extraClassDeclaration = [{
-    static constexpr llvm::StringRef baseType() { return "base_type"; }
-    mlir::Type getBaseType();
+    /// Get the type of the base object.
+    mlir::Type getBaseType() { return baseType(); }
   }];
 }
 
@@ -1836,6 +1831,8 @@
     Extract a value from an entity with a type composed of tuples, arrays,
     and/or derived types. Returns the value from entity with the type of the
     specified component. Cannot be used on values of `!fir.box` type.
+    It can also be used to access complex parts and elements of a character
+    string.
 
     Note that the entity ssa-value must be of compile-time known size in order
     to use this operation.
@@ -1897,8 +1894,8 @@
       auto loc = parser.getNameLoc();
       if (parser.parseOperandList(operands,
                                   mlir::OpAsmParser::Delimiter::None) ||
-          parser.parseRParen() ||
           parser.parseColonTypeList(types) ||
+          parser.parseRParen() ||
           parser.resolveOperands(operands, types, loc, result.operands))
         return mlir::failure();
     }
@@ -2146,9 +2143,11 @@
   let summary = "insert a new sub-value into a copy of an existing aggregate";
 
   let description = [{
-    Insert a value from an entity with a type composed of tuples, arrays,
+    Insert a value into an entity with a type composed of tuples, arrays,
     and/or derived types. Returns a new ssa value with the same type as the
     original entity. Cannot be used on values of `!fir.box` type.
+    It can also be used to set complex parts and elements of a character
+    string.
 
     Note that the entity ssa-value must be of compile-time known size in order
     to use this operation.
@@ -2169,6 +2168,26 @@
   let assemblyFormat = [{
     operands attr-dict `:` functional-type(operands, results)
   }];
+
+  let hasCanonicalizer = 1;
+}
+
+def fir_InsertOnRangeOp : fir_OneResultOp<"insert_on_range", [NoSideEffect]> {
+  let summary = "insert sub-value into a range on an existing sequence";
+
+  let description = [{
+    Insert a constant value into an entity with an array type. Returns a
+    new ssa value where the range of offsets from the original array have been
+    replaced with the constant. The result is an array type entity.
+  }];
+
+  let arguments = (ins fir_SequenceType:$seq, AnyType:$val,
+                       Variadic<Index>:$coor);
+  let results = (outs fir_SequenceType);
+
+  let assemblyFormat = [{
+    operands attr-dict `:` functional-type(operands, results)
+  }];
 }
 
 def fir_LenParamIndexOp : fir_OneResultOp<"len_param_index", [NoSideEffect]> {
@@ -2214,12 +2233,13 @@
       << ", " << (*this)->getAttr(typeAttrName());
   }];
 
-  let builders = [
-    OpBuilderDAG<(ins "StringRef":$fieldName, "Type":$recTy),
+  let builders = [OpBuilderDAG<(ins "llvm::StringRef":$fieldName,
+      "mlir::Type":$recTy),
     [{
       $_state.addAttribute(fieldAttrName(), $_builder.getStringAttr(fieldName));
       $_state.addAttribute(typeAttrName(), TypeAttr::get(recTy));
-    }]>];
+    }]
+  >];
 
   let extraClassDeclaration = [{
     static constexpr llvm::StringRef fieldAttrName() { return "field_id"; }
@@ -2247,10 +2267,7 @@
   }];
 
   let arguments = (ins Variadic<AnyType>:$results);
-  let builders = [
-    OpBuilderDAG<(ins),
-    [{/* do nothing */}]>
-  ];
+  let builders = [OpBuilderDAG<(ins), [{ /* do nothing */ }]>];
 
   let assemblyFormat = "($results^ `:` type($results))? attr-dict";
 
@@ -2272,7 +2289,7 @@
   let summary = "generalized loop operation";
   let description = [{
     Generalized high-level looping construct. This operation is similar to
-    MLIR's `loop.for`.
+    MLIR's `scf.for`.
 
     ```mlir
       %l = constant 0 : index
@@ -2281,7 +2298,7 @@
       fir.do_loop %i = %l to %u step %s unordered {
         %x = fir.convert %i : (index) -> i32
         %v = fir.call @compute(%x) : (i32) -> f32
-        %p = fir.coordinate_of %A, %i : (!fir.ref<f32>, index) -> !fir.ref<f32>
+        %p = fir.coordinate_of %A, %i : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
         fir.store %v to %p : !fir.ref<f32>
       }
     ```
@@ -2295,23 +2312,26 @@
     Index:$upperBound,
     Index:$step,
     Variadic<AnyType>:$initArgs,
-    OptionalAttr<UnitAttr>:$unordered
+    OptionalAttr<UnitAttr>:$unordered,
+    OptionalAttr<UnitAttr>:$finalValue
   );
-  let results = (outs
-    Variadic<AnyType>:$results
-  );
+  let results = (outs Variadic<AnyType>:$results);
   let regions = (region SizedRegion<1>:$region);
 
   let skipDefaultBuilders = 1;
   let builders = [
     OpBuilderDAG<(ins "mlir::Value":$lowerBound, "mlir::Value":$upperBound,
       "mlir::Value":$step, CArg<"bool", "false">:$unordered,
-      CArg<"ValueRange", "llvm::None">:$iterArgs,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>
+      CArg<"bool", "false">:$finalCountValue,
+      CArg<"mlir::ValueRange", "llvm::None">:$iterArgs,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attributes)>
   ];
 
   let extraClassDeclaration = [{
     static constexpr llvm::StringRef unorderedAttrName() { return "unordered"; }
+    static constexpr llvm::StringRef finalValueAttrName() {
+      return "finalValue";
+    }
 
     mlir::Value getInductionVar() { return getBody()->getArgument(0); }
     mlir::OpBuilder getBodyBuilder() {
@@ -2350,6 +2370,11 @@
       (*this)->setAttr(unorderedAttrName(),
                               mlir::UnitAttr::get(getContext()));
     }
+
+    mlir::BlockArgument iterArgToBlockArg(mlir::Value iterArg);
+    void resultToSourceOps(llvm::SmallVectorImpl<mlir::Value> &results,
+                           unsigned resultNum);
+    mlir::Value blockArgToSourceOp(unsigned blockArgNum);
   }];
 }
 
@@ -2374,28 +2399,31 @@
   let results = (outs Variadic<AnyType>:$results);
 
   let regions = (region
-    SizedRegion<1>:$whereRegion,
-    AnyRegion:$otherRegion
+    SizedRegion<1>:$thenRegion,
+    AnyRegion:$elseRegion
   );
 
   let skipDefaultBuilders = 1;
   let builders = [
-    OpBuilderDAG<(ins "Value":$cond, "bool":$withOtherRegion)>,
-    OpBuilderDAG<(ins "TypeRange":$resultTypes, "Value":$cond,
-      "bool":$withOtherRegion)>
+    OpBuilderDAG<(ins "mlir::Value":$cond, "bool":$withElseRegion)>,
+    OpBuilderDAG<(ins "mlir::TypeRange":$resultTypes, "mlir::Value":$cond,
+        "bool":$withElseRegion)>
   ];
 
   let extraClassDeclaration = [{
-    mlir::OpBuilder getWhereBodyBuilder() {
-      assert(!whereRegion().empty() && "Unexpected empty 'where' region.");
-      mlir::Block &body = whereRegion().front();
+    mlir::OpBuilder getThenBodyBuilder() {
+      assert(!thenRegion().empty() && "Unexpected empty 'where' region.");
+      mlir::Block &body = thenRegion().front();
       return mlir::OpBuilder(&body, std::prev(body.end()));
     }
-    mlir::OpBuilder getOtherBodyBuilder() {
-      assert(!otherRegion().empty() && "Unexpected empty 'other' region.");
-      mlir::Block &body = otherRegion().front();
+    mlir::OpBuilder getElseBodyBuilder() {
+      assert(!elseRegion().empty() && "Unexpected empty 'other' region.");
+      mlir::Block &body = elseRegion().front();
       return mlir::OpBuilder(&body, std::prev(body.end()));
     }
+
+    void resultToSourceOps(llvm::SmallVectorImpl<mlir::Value> &results,
+                           unsigned resultNum);
   }];
 }
 
@@ -2403,12 +2431,30 @@
     [DeclareOpInterfaceMethods<LoopLikeOpInterface>]> {
   let summary = "DO loop with early exit condition";
   let description = [{
-    This construct is useful for lowering implied-DO loops. It is very similar
-    to `fir::DoLoopOp` with the addition that it requires a single loop-carried
-    bool value that signals an early exit condition to the operation. A `true`
-    disposition means the next loop iteration should proceed. A `false`
-    indicates that the `fir.iterate_while` operation should terminate and
-    return its iteration arguments.
+    This single-entry, single-exit looping construct is useful for lowering
+    counted loops that can exit early such as, for instance, implied-DO loops.
+    It is very similar to `fir::DoLoopOp` with the addition that it requires
+    a single loop-carried bool value that signals an early exit condition to
+    the operation. A `true` disposition means the next loop iteration should
+    proceed. A `false` indicates that the `fir.iterate_while` operation should
+    terminate and return its iteration arguments. This is a degenerate counted
+    loop in that the loop is not guaranteed to execute all iterations.
+
+    An example iterate_while that returns the counter value, the early
+    termination condition, and an extra loop-carried value is shown here. This
+    loop counts from %lo to %up (inclusive), stepping by %c1, so long as the
+    early exit (%ok) is true. The iter_args %sh value is also carried by the
+    loop. The result triple is the values of %i=phi(%lo,%i+%c1),
+    %ok=phi(%okIn,%okNew), and %sh=phi(%shIn,%shNew) from the last executed
+    iteration.
+
+    ```mlir
+      %v:3 = fir.iterate_while (%i = %lo to %up step %c1) and (%ok = %okIn) iter_args(%sh = %shIn) -> (index, i1, i16) {
+        %shNew = fir.call @bar(%sh) : (i16) -> i16
+        %okNew = fir.call @foo(%sh) : (i16) -> i1
+        fir.result %i, %okNew, %shNew : index, i1, i16
+      }
+    ```
   }];
 
   let arguments = (ins
@@ -2416,23 +2462,25 @@
     Index:$upperBound,
     Index:$step,
     I1:$iterateIn,
-    Variadic<AnyType>:$initArgs
+    Variadic<AnyType>:$initArgs,
+    OptionalAttr<UnitAttr>:$finalValue
   );
-  let results = (outs
-    I1:$iterateResult,
-    Variadic<AnyType>:$results
-  );
+  let results = (outs Variadic<AnyType>:$results);
   let regions = (region SizedRegion<1>:$region);
 
   let skipDefaultBuilders = 1;
   let builders = [
     OpBuilderDAG<(ins "mlir::Value":$lowerBound, "mlir::Value":$upperBound,
       "mlir::Value":$step, "mlir::Value":$iterate,
-      CArg<"ValueRange", "llvm::None">:$iterArgs,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>
+      CArg<"bool", "false">:$finalCountValue,
+      CArg<"mlir::ValueRange", "llvm::None">:$iterArgs,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attributes)>
   ];
 
   let extraClassDeclaration = [{
+    static constexpr llvm::StringRef finalValueAttrName() {
+      return "finalValue";
+    }
     mlir::Block *getBody() { return &region().front(); }
     mlir::Value getIterateVar() { return getBody()->getArgument(1); }
     mlir::Value getInductionVar() { return getBody()->getArgument(0); }
@@ -2464,6 +2512,11 @@
     unsigned getNumIterOperands() {
       return (*this)->getNumOperands() - getNumControlOperands();
     }
+
+    mlir::BlockArgument iterArgToBlockArg(mlir::Value iterArg);
+    void resultToSourceOps(llvm::SmallVectorImpl<mlir::Value> &results,
+                           unsigned resultNum);
+    mlir::Value blockArgToSourceOp(unsigned blockArgNum);
   }];
 }
 
@@ -2471,8 +2524,7 @@
 // Procedure call operations
 //===----------------------------------------------------------------------===//
 
-def fir_CallOp : fir_Op<"call",
-    [MemoryEffects<[MemAlloc, MemFree, MemRead, MemWrite]>]> {
+def fir_CallOp : fir_Op<"call", [CallOpInterface]> {
   let summary = "call a procedure";
 
   let description = [{
@@ -2491,19 +2543,63 @@
     OptionalAttr<SymbolRefAttr>:$callee,
     Variadic<AnyType>:$args
   );
-
   let results = (outs Variadic<AnyType>);
 
   let parser = "return parseCallOp(parser, result);";
   let printer = "printCallOp(p, *this);";
 
+  let builders = [
+    OpBuilderDAG<(ins "mlir::FuncOp":$callee,
+        CArg<"mlir::ValueRange", "{}">:$operands),
+    [{
+      $_state.addOperands(operands);
+      $_state.addAttribute(calleeAttrName(),
+        $_builder.getSymbolRefAttr(callee));
+      $_state.addTypes(callee.getType().getResults());
+    }]>,
+    OpBuilderDAG<(ins "mlir::SymbolRefAttr":$callee,
+        "llvm::ArrayRef<mlir::Type>":$results,
+        CArg<"mlir::ValueRange", "{}">:$operands),
+    [{
+      $_state.addOperands(operands);
+      $_state.addAttribute(calleeAttrName(), callee);
+      $_state.addTypes(results);
+    }]>,
+    OpBuilderDAG<(ins "llvm::StringRef":$callee,
+        "llvm::ArrayRef<mlir::Type>":$results,
+        CArg<"mlir::ValueRange", "{}">:$operands),
+    [{
+      build($_builder, $_state, $_builder.getSymbolRefAttr(callee), results,
+            operands);
+    }]>];
+
   let extraClassDeclaration = [{
     static constexpr StringRef calleeAttrName() { return "callee"; }
+
+    mlir::FunctionType getFunctionType();
+
+    /// Get the argument operands to the called function.
+    operand_range getArgOperands() {
+      if (auto calling =
+          (*this)->getAttrOfType<SymbolRefAttr>(calleeAttrName()))
+        return {arg_operand_begin(), arg_operand_end()};
+      return {arg_operand_begin() + 1, arg_operand_end()};
+    }
+
+    operand_iterator arg_operand_begin() { return operand_begin(); }
+    operand_iterator arg_operand_end() { return operand_end(); }
+
+    /// Return the callee of this operation.
+    CallInterfaceCallable getCallableForCallee() {
+      if (auto calling =
+          (*this)->getAttrOfType<SymbolRefAttr>(calleeAttrName()))
+        return calling;
+      return getOperand(0);
+    }
   }];
 }
 
-def fir_DispatchOp : fir_Op<"dispatch",
-    [MemoryEffects<[MemAlloc, MemFree, MemRead, MemWrite]>]> {
+def fir_DispatchOp : fir_Op<"dispatch", []> {
   let summary = "call a type-bound procedure";
 
   let description = [{
@@ -2531,10 +2627,11 @@
     llvm::StringRef calleeName;
     if (failed(parser.parseOptionalKeyword(&calleeName))) {
       mlir::StringAttr calleeAttr;
-      if (parser.parseAttribute(calleeAttr, "method", result.attributes))
+      if (parser.parseAttribute(calleeAttr, methodAttrName(),
+                                result.attributes))
         return mlir::failure();
     } else {
-      result.addAttribute("method",
+      result.addAttribute(methodAttrName(),
           parser.getBuilder().getStringAttr(calleeName));
     }
     if (parser.parseOperandList(operands,
@@ -2545,22 +2642,19 @@
         parser.resolveOperands(
             operands, calleeType.getInputs(), calleeLoc, result.operands))
       return mlir::failure();
-    result.addAttribute("fn_type", mlir::TypeAttr::get(calleeType));
     return mlir::success();
   }];
 
   let printer = [{
-    p << getOperationName() << ' ' << (*this)->getAttr("method") << '(';
+    p << getOperationName() << ' ' << (*this)->getAttr(methodAttrName()) << '(';
     p.printOperand(object());
-    if (arg_operand_begin() != arg_operand_end()) {
+    if (!args().empty()) {
       p << ", ";
       p.printOperands(args());
     }
-    p << ')';
-    p.printOptionalAttrDict((*this)->getAttrs(), {"fn_type", "method"});
-    auto resTy{getResultTypes()};
-    llvm::SmallVector<mlir::Type, 8> argTy(getOperandTypes());
-    p << " : " << mlir::FunctionType::get(getContext(), argTy, resTy);
+    p << ") : ";
+    p.printFunctionalType((*this)->getOperandTypes(),
+        (*this)->getResultTypes());
   }];
 
   let extraClassDeclaration = [{
@@ -2568,9 +2662,13 @@
     operand_range getArgOperands() {
       return {arg_operand_begin(), arg_operand_end()};
     }
+    // operand[0] is the object (of box type)
     operand_iterator arg_operand_begin() { return operand_begin() + 1; }
     operand_iterator arg_operand_end() { return operand_end(); }
-    llvm::StringRef passArgAttrName() { return "pass_arg_pos"; }
+    static constexpr llvm::StringRef passArgAttrName() {
+      return "pass_arg_pos";
+    }
+    static constexpr llvm::StringRef methodAttrName() { return "method"; }
     unsigned passArgPos();
   }];
 }
@@ -2591,12 +2689,13 @@
     ```
   }];
 
-  let results = (outs fir_SequenceType);
+  let results = (outs fir_CharacterType);
 
   let parser = [{
     auto &builder = parser.getBuilder();
     mlir::Attribute val;
     mlir::NamedAttrList attrs;
+    llvm::SMLoc trailingTypeLoc;
     if (parser.parseAttribute(val, "fake", attrs))
       return mlir::failure();
     if (auto v = val.dyn_cast<mlir::StringAttr>())
@@ -2611,12 +2710,15 @@
     if (parser.parseLParen() ||
         parser.parseAttribute(sz, size(), result.attributes) ||
         parser.parseRParen() ||
+        parser.getCurrentLocation(&trailingTypeLoc) ||
         parser.parseColonType(type))
       return mlir::failure();
-    if (!(type.isa<fir::CharacterType>() || type.isa<mlir::IntegerType>()))
-      return parser.emitError(parser.getCurrentLocation(),
+    auto charTy = type.dyn_cast<fir::CharacterType>();
+    if (!charTy)
+      return parser.emitError(trailingTypeLoc,
                               "must have character type");
-    type = fir::SequenceType::get(type.getContext(), {sz.getInt()}, type, {});
+    type = fir::CharacterType::get(builder.getContext(), charTy.getFKind(),
+                                   sz.getInt());
     if (!type || parser.addTypesToList(type, result.types))
       return mlir::failure();
     return mlir::success();
@@ -2625,20 +2727,17 @@
   let printer = [{
     p << getOperationName() << ' ' << getValue() << '(';
     p << getSize().cast<mlir::IntegerAttr>().getValue() << ") : ";
-    p.printType(getType().cast<fir::SequenceType>().getEleTy());
+    p.printType(getType());
   }];
 
   let verifier = [{
     if (getSize().cast<mlir::IntegerAttr>().getValue().isNegative())
       return emitOpError("size must be non-negative");
-    auto eleTy = getType().cast<fir::SequenceType>().getEleTy();
-    if (!eleTy.isa<fir::CharacterType>())
-      return emitOpError("must have !fir.char type");
     if (auto xl = (*this)->getAttr(xlist())) {
       auto xList = xl.cast<mlir::ArrayAttr>();
       for (auto a : xList)
         if (!a.isa<mlir::IntegerAttr>())
-	  return emitOpError("values in list must be integers");
+	    return emitOpError("values in list must be integers");
     }
     return mlir::success();
   }];
@@ -2686,32 +2785,6 @@
   let printer = [{ return printUnaryOp(this->getOperation(), p); }];
 }
 
-def FirRealAttr : Attr<CPred<"$_self.isa<fir::RealAttr>()">, "FIR real attr"> {
-  let storageType = [{ fir::RealAttr }];
-  let returnType = [{ llvm::APFloat }];
-}
-
-def fir_ConstfOp : fir_Op<"constf", [NoSideEffect]> {
-  let summary = "create a floating point constant";
-
-  let description = [{
-    A floating-point constant. This operation is to augment MLIR to be able
-    to represent APFloat values that are not supported in the standard dialect.
-  }];
-
-  let arguments = (ins FirRealAttr:$constant);
-
-  let results = (outs fir_RealType:$res);
-
-  let assemblyFormat = "`(` $constant `)` attr-dict `:` type($res)";
-
-  let verifier = [{
-    if (!getType().isa<fir::RealType>())
-      return emitOpError("must be a !fir.real type");
-    return mlir::success();
-  }];
-}
-
 class RealUnaryArithmeticOp<string mnemonic, list<OpTrait> traits = []> :
       fir_UnaryArithmeticOp<mnemonic, traits>,
       Arguments<(ins AnyRealLike:$operand)>;
@@ -2748,9 +2821,8 @@
 
   let results = (outs AnyLogicalLike);
 
-  let builders = [
-    OpBuilderDAG<(ins "CmpFPredicate":$predicate, "Value":$lhs, "Value":$rhs),
-    [{
+  let builders = [OpBuilderDAG<(ins "mlir::CmpFPredicate":$predicate,
+    "mlir::Value":$lhs, "mlir::Value":$rhs), [{
       buildCmpFOp($_builder, $_state, predicate, lhs, rhs);
   }]>];
 
@@ -2857,9 +2929,8 @@
 
   let printer = "printCmpcOp(p, *this);";
 
-  let builders = [
-    OpBuilderDAG<(ins "CmpFPredicate":$predicate, "Value":$lhs, "Value":$rhs),
-    [{
+  let builders = [OpBuilderDAG<(ins "mlir::CmpFPredicate":$predicate,
+    "mlir::Value":$lhs, "mlir::Value":$rhs), [{
       buildCmpCOp($_builder, $_state, predicate, lhs, rhs);
   }]>];
 
@@ -2882,7 +2953,8 @@
 
   let description = [{
     Convert a symbol (a function or global reference) to an SSA-value to be
-    used in other Operations.
+    used in other Operations. References to Fortran symbols are distinguished
+    via this operation from other arbitrary constant values.
 
     ```mlir
       %p = fir.address_of(@symbol) : !fir.ref<f64>
@@ -2891,7 +2963,7 @@
 
   let arguments = (ins SymbolRefAttr:$symbol);
 
-  let results = (outs fir_ReferenceType:$resTy);
+  let results = (outs AnyAddressableLike:$resTy);
 
   let assemblyFormat = "`(` $symbol `)` attr-dict `:` type($resTy)";
 }
@@ -2943,6 +3015,7 @@
     static bool isFloatCompatible(mlir::Type ty);
     static bool isPointerCompatible(mlir::Type ty);
   }];
+  let hasCanonicalizer = 1;
 }
 
 def FortranTypeAttr : Attr<And<[CPred<"$_self.isa<TypeAttr>()">,
@@ -2989,9 +3062,7 @@
     p.printOptionalAttrDict((*this)->getAttrs(), {"in_type"});
   }];
 
-  let builders = [
-    OpBuilderDAG<(ins "mlir::TypeAttr":$inty)>
-  ];
+  let builders = [OpBuilderDAG<(ins "mlir::TypeAttr":$inty)>];
 
   let verifier = [{
     mlir::Type resultTy = getType();
@@ -3095,22 +3166,24 @@
 
   let skipDefaultBuilders = 1;
   let builders = [
-    OpBuilderDAG<(ins "StringRef":$name, "Type":$type,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>,
-    OpBuilderDAG<(ins "StringRef":$name, "bool":$isConstant, "Type":$type,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>,
-    OpBuilderDAG<(ins "StringRef":$name, "Type":$type,
-      CArg<"StringAttr", "{}">:$linkage,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>,
-    OpBuilderDAG<(ins "StringRef":$name, "bool":$isConstant, "Type":$type,
-      CArg<"StringAttr", "{}">:$linkage,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>,
-    OpBuilderDAG<(ins "StringRef":$name, "Type":$type, "Attribute":$initVal,
-      CArg<"StringAttr", "{}">:$linkage,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>,
-    OpBuilderDAG<(ins "StringRef":$name, "bool":$isConstant, "Type":$type,
-      "Attribute":$initVal, CArg<"StringAttr", "{}">:$linkage,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>,
+    OpBuilderDAG<(ins "llvm::StringRef":$name, "mlir::Type":$type,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attrs)>,
+    OpBuilderDAG<(ins "llvm::StringRef":$name, "bool":$isConstant,
+      "mlir::Type":$type,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attrs)>,
+    OpBuilderDAG<(ins "llvm::StringRef":$name, "mlir::Type":$type,
+      CArg<"mlir::StringAttr", "{}">:$linkage,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attrs)>,
+    OpBuilderDAG<(ins "llvm::StringRef":$name, "bool":$isConstant,
+      "mlir::Type":$type, CArg<"mlir::StringAttr", "{}">:$linkage,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attrs)>,
+    OpBuilderDAG<(ins "llvm::StringRef":$name, "mlir::Type":$type,
+      "mlir::Attribute":$initVal, CArg<"mlir::StringAttr", "{}">:$linkage,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attrs)>,
+    OpBuilderDAG<(ins "llvm::StringRef":$name, "bool":$isConstant,
+      "mlir::Type":$type, "mlir::Attribute":$initVal,
+      CArg<"mlir::StringAttr", "{}">:$linkage,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attrs)>,
   ];
 
   let extraClassDeclaration = [{
@@ -3271,8 +3344,8 @@
 
   let skipDefaultBuilders = 1;
   let builders = [
-    OpBuilderDAG<(ins "StringRef":$name, "Type":$type,
-      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs),
+    OpBuilderDAG<(ins "llvm::StringRef":$name, "mlir::Type":$type,
+      CArg<"llvm::ArrayRef<mlir::NamedAttribute>", "{}">:$attrs),
     [{
       $_state.addAttribute(mlir::SymbolTable::getSymbolAttrName(),
                            $_builder.getStringAttr(name));
@@ -3341,4 +3414,42 @@
   }];
 }
 
+def fir_AbsentOp : fir_OneResultOp<"absent", [NoSideEffect]> {
+  let summary = "create value to be passed for absent optional function argument";
+  let description = [{
+    Given the type of a function argument, create a value that will signal that
+    an optional argument is absent in the call. On the caller side, fir.is_present
+    can be used to query if the value of an optional argument was created with
+    a fir.absent operation.
+    It is undefined to use a value that was created by a fir.absent op in any other
+    operation than fir.call and fir.is_present.
+    ```mlir
+      %1 = fir.absent fir.box<fir.array<?xf32>>
+      fir.call @_QPfoo(%1) : (fir.box<fir.array<?xf32>>) -> ()
+    ```
+  }];
+
+  let results = (outs AnyRefOrBoxLike:$intype);
+
+  let assemblyFormat = "type($intype) attr-dict";
+}
+
+def fir_IsPresentOp : fir_SimpleOp<"is_present", [NoSideEffect]> {
+  let summary = "is this optional function argument present?";
+
+  let description = [{
+    Determine if an optional function argument is PRESENT (i.e. that it was not
+    created by a fir.absent op on the caller side).
+    ```mlir
+      func @_QPfoo(%arg0: !fir.box<!fir.array<?xf32>>) {
+        %0 = fir.is_present %arg0 : (!fir.box<!fir.array<?xf32>>) -> i1
+        ...
+    ```
+  }];
+
+  let arguments = (ins AnyRefOrBoxLike:$val);
+
+  let results = (outs BoolLike);
+}
+
 #endif
diff --git a/lib/Lower/CharacterExpr.cpp b/lib/Lower/CharacterExpr.cpp
index eadf934..ab72047 100644
--- a/lib/Lower/CharacterExpr.cpp
+++ b/lib/Lower/CharacterExpr.cpp
@@ -341,36 +341,7 @@
 
 mlir::Value Fortran::lower::CharacterExprHelper::createLenTrim(
     const fir::CharBoxValue &str) {
-  // Note: Runtime for LEN_TRIM should also be available at some
-  // point. For now use an inlined implementation.
-  auto indexType = builder.getIndexType();
-  auto len = builder.createConvert(loc, indexType, str.getLen());
-  auto one = builder.createIntegerConstant(loc, indexType, 1);
-  auto minusOne = builder.createIntegerConstant(loc, indexType, -1);
-  auto zero = builder.createIntegerConstant(loc, indexType, 0);
-  auto trueVal = builder.createIntegerConstant(loc, builder.getI1Type(), 1);
-  auto blank = createBlankConstantCode(getCharacterType(str));
-  mlir::Value lastChar = builder.create<mlir::SubIOp>(loc, len, one);
-
-  auto iterWhile = builder.create<fir::IterWhileOp>(
-      loc, lastChar, zero, minusOne, trueVal, lastChar);
-  auto insPt = builder.saveInsertionPoint();
-  builder.setInsertionPointToStart(iterWhile.getBody());
-  auto index = iterWhile.getInductionVar();
-  // Look for first non-blank from the right of the character.
-  auto c = createLoadCharAt(str, index);
-  c = builder.createConvert(loc, blank.getType(), c);
-  auto isBlank =
-      builder.create<mlir::CmpIOp>(loc, mlir::CmpIPredicate::eq, blank, c);
-  llvm::SmallVector<mlir::Value, 2> results = {isBlank, index};
-  builder.create<fir::ResultOp>(loc, results);
-  builder.restoreInsertionPoint(insPt);
-  // Compute length after iteration (zero if all blanks)
-  mlir::Value newLen =
-      builder.create<mlir::AddIOp>(loc, iterWhile.getResult(1), one);
-  auto result =
-      builder.create<SelectOp>(loc, iterWhile.getResult(0), zero, newLen);
-  return builder.createConvert(loc, getLengthType(), result);
+  return {};
 }
 
 mlir::Value Fortran::lower::CharacterExprHelper::createTemp(mlir::Type type,
diff --git a/lib/Lower/IO.cpp b/lib/Lower/IO.cpp
index 3c23eb2..aae12aa 100644
--- a/lib/Lower/IO.cpp
+++ b/lib/Lower/IO.cpp
@@ -182,7 +182,7 @@
           : builder.create<fir::IfOp>(loc, ok, /*withOtherwise=*/false);
   if (!insertPt.isSet())
     insertPt = builder.saveInsertionPoint();
-  builder.setInsertionPointToStart(&whereOp.whereRegion().front());
+  builder.setInsertionPointToStart(&whereOp.thenRegion().front());
 }
 
 template <typename D>
@@ -414,10 +414,10 @@
   for (auto *op = builder.getBlock()->getParentOp(); isa<fir::IfOp>(op);
        op = op->getBlock()->getParentOp()) {
     auto whereOp = dyn_cast<fir::IfOp>(op);
-    auto *lastOp = &whereOp.whereRegion().front().back();
+    auto *lastOp = &whereOp.thenRegion().front().back();
     builder.setInsertionPointAfter(lastOp);
     builder.create<fir::ResultOp>(loc, lastOp->getResult(0)); // runtime result
-    builder.setInsertionPointToStart(&whereOp.otherRegion().front());
+    builder.setInsertionPointToStart(&whereOp.elseRegion().front());
     builder.create<fir::ResultOp>(loc, falseValue); // known false result
   }
   builder.restoreInsertionPoint(insertPt);
diff --git a/lib/Optimizer/Dialect/FIRDialect.cpp b/lib/Optimizer/Dialect/FIRDialect.cpp
index 696b552..889b5ef 100644
--- a/lib/Optimizer/Dialect/FIRDialect.cpp
+++ b/lib/Optimizer/Dialect/FIRDialect.cpp
@@ -22,7 +22,7 @@
   addTypes<BoxType, BoxCharType, BoxProcType, CharacterType, fir::ComplexType,
            FieldType, HeapType, fir::IntegerType, LenType, LogicalType,
            PointerType, RealType, RecordType, ReferenceType, SequenceType,
-           ShapeType, ShapeShiftType, SliceType, TypeDescType,
+           ShapeType, ShapeShiftType, ShiftType, SliceType, TypeDescType,
            fir::VectorType>();
   addAttributes<ClosedIntervalAttr, ExactTypeAttr, LowerBoundAttr,
                 PointIntervalAttr, RealAttr, SubclassAttr, UpperBoundAttr>();
diff --git a/lib/Optimizer/Dialect/FIROps.cpp b/lib/Optimizer/Dialect/FIROps.cpp
index 47211e2..79b1a33 100644
--- a/lib/Optimizer/Dialect/FIROps.cpp
+++ b/lib/Optimizer/Dialect/FIROps.cpp
@@ -19,6 +19,7 @@
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/Diagnostics.h"
 #include "mlir/IR/Matchers.h"
+#include "mlir/IR/PatternMatch.h"
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/ADT/TypeSwitch.h"
 
@@ -246,6 +247,11 @@
 // CallOp
 //===----------------------------------------------------------------------===//
 
+mlir::FunctionType fir::CallOp::getFunctionType() {
+  return mlir::FunctionType::get(getContext(), getOperandTypes(),
+                                 getResultTypes());
+}
+
 static void printCallOp(mlir::OpAsmPrinter &p, fir::CallOp &op) {
   auto callee = op.callee();
   bool isDirect = callee.hasValue();
@@ -291,12 +297,9 @@
   } else {
     auto funcArgs =
         llvm::ArrayRef<mlir::OpAsmParser::OperandType>(operands).drop_front();
-    llvm::SmallVector<mlir::Value, 8> resultArgs(
-        result.operands.begin() + (result.operands.empty() ? 0 : 1),
-        result.operands.end());
     if (parser.resolveOperand(operands[0], funcType, result.operands) ||
         parser.resolveOperands(funcArgs, funcType.getInputs(),
-                               parser.getNameLoc(), resultArgs))
+                               parser.getNameLoc(), result.operands))
       return mlir::failure();
   }
   result.addTypes(funcType.getResults());
@@ -403,6 +406,10 @@
 // ConvertOp
 //===----------------------------------------------------------------------===//
 
+void fir::ConvertOp::getCanonicalizationPatterns(
+    OwningRewritePatternList &results, MLIRContext *context) {
+}
+
 mlir::OpFoldResult fir::ConvertOp::fold(llvm::ArrayRef<mlir::Attribute> opnds) {
   if (value().getType() == getType())
     return value();
@@ -425,8 +432,7 @@
 
 bool fir::ConvertOp::isIntegerCompatible(mlir::Type ty) {
   return ty.isa<mlir::IntegerType>() || ty.isa<mlir::IndexType>() ||
-         ty.isa<fir::IntegerType>() || ty.isa<fir::LogicalType>() ||
-         ty.isa<fir::CharacterType>();
+         ty.isa<fir::IntegerType>() || ty.isa<fir::LogicalType>();
 }
 
 bool fir::ConvertOp::isFloatCompatible(mlir::Type ty) {
@@ -436,67 +442,68 @@
 bool fir::ConvertOp::isPointerCompatible(mlir::Type ty) {
   return ty.isa<fir::ReferenceType>() || ty.isa<fir::PointerType>() ||
          ty.isa<fir::HeapType>() || ty.isa<mlir::MemRefType>() ||
-         ty.isa<fir::TypeDescType>();
+         ty.isa<mlir::FunctionType>() || ty.isa<fir::TypeDescType>();
 }
 
 //===----------------------------------------------------------------------===//
 // CoordinateOp
 //===----------------------------------------------------------------------===//
 
-static mlir::ParseResult parseCoordinateOp(mlir::OpAsmParser &parser,
-                                           mlir::OperationState &result) {
-  llvm::ArrayRef<mlir::Type> allOperandTypes;
-  llvm::ArrayRef<mlir::Type> allResultTypes;
-  llvm::SMLoc allOperandLoc = parser.getCurrentLocation();
-  llvm::SmallVector<mlir::OpAsmParser::OperandType, 4> allOperands;
-  if (parser.parseOperandList(allOperands))
-    return failure();
-  if (parser.parseOptionalAttrDict(result.attributes))
-    return failure();
-  if (parser.parseColon())
-    return failure();
+static void print(mlir::OpAsmPrinter &p, fir::CoordinateOp op) {
+  p << op.getOperationName() << ' ' << op.ref() << ", " << op.coor();
+  p.printOptionalAttrDict(op->getAttrs(), /*elideAttrs=*/{"baseType"});
+  p << " : ";
+  p.printFunctionalType(op.getOperandTypes(), op->getResultTypes());
+}
 
+static mlir::ParseResult parseCoordinateCustom(mlir::OpAsmParser &parser,
+                                               mlir::OperationState &result) {
+  mlir::OpAsmParser::OperandType memref;
+  if (parser.parseOperand(memref) || parser.parseComma())
+    return mlir::failure();
+  llvm::SmallVector<mlir::OpAsmParser::OperandType, 8> coorOperands;
+  if (parser.parseOperandList(coorOperands))
+    return mlir::failure();
+  llvm::SmallVector<mlir::OpAsmParser::OperandType, 16> allOperands;
+  allOperands.push_back(memref);
+  allOperands.append(coorOperands.begin(), coorOperands.end());
   mlir::FunctionType funcTy;
-  if (parser.parseType(funcTy))
-    return failure();
-  allOperandTypes = funcTy.getInputs();
-  allResultTypes = funcTy.getResults();
-  result.addTypes(allResultTypes);
-  if (parser.resolveOperands(allOperands, allOperandTypes, allOperandLoc,
+  auto loc = parser.getCurrentLocation();
+  if (parser.parseOptionalAttrDict(result.attributes) ||
+      parser.parseColonType(funcTy) ||
+      parser.resolveOperands(allOperands, funcTy.getInputs(), loc,
                              result.operands))
     return failure();
-  if (funcTy.getNumInputs()) {
-    // No inputs handled by verify
-    result.addAttribute(fir::CoordinateOp::baseType(),
-                        mlir::TypeAttr::get(funcTy.getInput(0)));
+  parser.addTypesToList(funcTy.getResults(), result.types);
+  result.addAttribute("baseType", mlir::TypeAttr::get(funcTy.getInput(0)));
+  return mlir::success();
+}
+
+static mlir::LogicalResult verify(fir::CoordinateOp op) {
+  auto refTy = op.ref().getType();
+  if (fir::isa_ref_type(refTy)) {
+    auto eleTy = fir::dyn_cast_ptrEleTy(refTy);
+    if (auto arrTy = eleTy.dyn_cast<fir::SequenceType>()) {
+      if (arrTy.hasUnknownShape())
+        return op.emitOpError("cannot find coordinate in unknown shape");
+      if (arrTy.getConstantRows() < arrTy.getDimension() - 1)
+        return op.emitOpError("cannot find coordinate with unknown extents");
+    }
+    if (!(fir::isa_aggregate(eleTy) || fir::isa_complex(eleTy) ||
+          fir::isa_char_string(eleTy)))
+      return op.emitOpError("cannot apply coordinate_of to this type");
   }
-  return success();
-}
-
-mlir::Type fir::CoordinateOp::getBaseType() {
-  return (*this)
-      ->getAttr(CoordinateOp::baseType())
-      .cast<mlir::TypeAttr>()
-      .getValue();
-}
-
-void fir::CoordinateOp::build(OpBuilder &, OperationState &result,
-                              mlir::Type resType, ValueRange operands,
-                              ArrayRef<NamedAttribute> attrs) {
-  assert(operands.size() >= 1u && "mismatched number of parameters");
-  result.addOperands(operands);
-  result.addAttribute(fir::CoordinateOp::baseType(),
-                      mlir::TypeAttr::get(operands[0].getType()));
-  result.attributes.append(attrs.begin(), attrs.end());
-  result.addTypes({resType});
-}
-
-void fir::CoordinateOp::build(OpBuilder &builder, OperationState &result,
-                              mlir::Type resType, mlir::Value ref,
-                              ValueRange coor, ArrayRef<NamedAttribute> attrs) {
-  llvm::SmallVector<mlir::Value, 16> operands{ref};
-  operands.append(coor.begin(), coor.end());
-  build(builder, result, resType, operands, attrs);
+  // Recovering a LEN type parameter only makes sense from a boxed value. For a
+  // bare reference, the LEN type parameters must be passed as additional
+  // arguments to `op`.
+  for (auto co : op.coor())
+    if (dyn_cast_or_null<fir::LenParamIndexOp>(co.getDefiningOp())) {
+      if (op.getNumOperands() != 2)
+        return op.emitOpError("len_param_index must be last argument");
+      if (!op.ref().getType().isa<BoxType>())
+        return op.emitOpError("len_param_index must be used on box type");
+    }
+  return mlir::success();
 }
 
 //===----------------------------------------------------------------------===//
@@ -504,8 +511,8 @@
 //===----------------------------------------------------------------------===//
 
 mlir::FunctionType fir::DispatchOp::getFunctionType() {
-  auto attr = (*this)->getAttr("fn_type").cast<mlir::TypeAttr>();
-  return attr.getValue().cast<mlir::FunctionType>();
+  return mlir::FunctionType::get(getContext(), getOperandTypes(),
+                                 getResultTypes());
 }
 
 //===----------------------------------------------------------------------===//
@@ -522,40 +529,45 @@
 // EmboxOp
 //===----------------------------------------------------------------------===//
 
-static mlir::ParseResult parseEmboxOp(mlir::OpAsmParser &parser,
-                                      mlir::OperationState &result) {
-  mlir::FunctionType type;
-  llvm::SmallVector<mlir::OpAsmParser::OperandType, 8> operands;
-  mlir::OpAsmParser::OperandType memref;
-  if (parser.parseOperand(memref))
-    return mlir::failure();
-  operands.push_back(memref);
-  auto &builder = parser.getBuilder();
-  if (!parser.parseOptionalLParen()) {
-    if (parser.parseOperandList(operands, mlir::OpAsmParser::Delimiter::None) ||
-        parser.parseRParen())
-      return mlir::failure();
-    auto lens = builder.getI32IntegerAttr(operands.size());
-    result.addAttribute(fir::EmboxOp::lenpName(), lens);
+static mlir::LogicalResult verify(fir::EmboxOp op) {
+  auto eleTy = fir::dyn_cast_ptrEleTy(op.memref().getType());
+  if (!eleTy)
+    return op.emitOpError("must embox a memory reference type");
+  bool isArray = false;
+  if (auto seqTy = eleTy.dyn_cast<fir::SequenceType>()) {
+    eleTy = seqTy.getEleTy();
+    isArray = true;
   }
-  if (!parser.parseOptionalComma()) {
-    mlir::OpAsmParser::OperandType dims;
-    if (parser.parseOperand(dims))
-      return mlir::failure();
-    operands.push_back(dims);
-  } else if (!parser.parseOptionalLSquare()) {
-    mlir::AffineMapAttr map;
-    if (parser.parseAttribute(map, fir::EmboxOp::layoutName(),
-                              result.attributes) ||
-        parser.parseRSquare())
-      return mlir::failure();
+  if (op.hasLenParams()) {
+    auto lenPs = op.numLenParams();
+    if (auto rt = eleTy.dyn_cast<fir::RecordType>()) {
+      if (lenPs != rt.getNumLenParams())
+        return op.emitOpError("number of LEN params does not correspond"
+                              " to the !fir.type type");
+    } else if (auto strTy = eleTy.dyn_cast<fir::CharacterType>()) {
+      if (strTy.getLen() != fir::CharacterType::unknownLen())
+        return op.emitOpError("CHARACTER already has static LEN");
+    } else {
+      return op.emitOpError("LEN parameters require CHARACTER or derived type");
+    }
+    for (auto lp : op.lenParams())
+      if (!fir::isa_integer(lp.getType()))
+        return op.emitOpError("LEN parameters must be integral type");
   }
-  if (parser.parseOptionalAttrDict(result.attributes) ||
-      parser.parseColonType(type) ||
-      parser.resolveOperands(operands, type.getInputs(), parser.getNameLoc(),
-                             result.operands) ||
-      parser.addTypesToList(type.getResults(), result.types))
-    return mlir::failure();
+  if (op.getShape()) {
+    auto shapeTy = op.getShape().getType();
+    if (!(shapeTy.isa<fir::ShapeType>() || shapeTy.isa<ShapeShiftType>()))
+      return op.emitOpError("must be shape or shapeshift type");
+    if (!isArray)
+      return op.emitOpError("shape must not be provided for a scalar");
+  }
+  if (op.getSlice()) {
+    auto sliceTy = op.getSlice().getType();
+    if (!sliceTy.isa<fir::SliceType>())
+      return op.emitOpError("must be a slice type");
+    if (!isArray)
+      return op.emitOpError("slice must not be provided for a scalar");
+  }
   return mlir::success();
 }
 
@@ -579,7 +591,7 @@
   auto &builder = parser.getBuilder();
   if (mlir::succeeded(parser.parseOptionalKeyword(&linkage))) {
     if (fir::GlobalOp::verifyValidLinkage(linkage))
-      return failure();
+      return mlir::failure();
     mlir::StringAttr linkAttr = builder.getStringAttr(linkage);
     result.addAttribute(fir::GlobalOp::linkageAttrName(), linkAttr);
   }
@@ -588,7 +600,7 @@
   mlir::SymbolRefAttr nameAttr;
   if (parser.parseAttribute(nameAttr, fir::GlobalOp::symbolAttrName(),
                             result.attributes))
-    return failure();
+    return mlir::failure();
   result.addAttribute(mlir::SymbolTable::getSymbolAttrName(),
                       builder.getStringAttr(nameAttr.getRootReference()));
 
@@ -598,7 +610,7 @@
     if (parser.parseAttribute(attr, fir::GlobalOp::initValAttrName(),
                               result.attributes) ||
         parser.parseRParen())
-      return failure();
+      return mlir::failure();
     simpleInitializer = true;
   }
 
@@ -610,7 +622,7 @@
 
   mlir::Type globalType;
   if (parser.parseColonType(globalType))
-    return failure();
+    return mlir::failure();
 
   result.addAttribute(fir::GlobalOp::typeAttrName(),
                       mlir::TypeAttr::get(globalType));
@@ -619,11 +631,13 @@
     result.addRegion();
   } else {
     // Parse the optional initializer body.
-    if (parser.parseRegion(*result.addRegion(), llvm::None, llvm::None))
-      return failure();
+    auto parseResult = parser.parseOptionalRegion(
+        *result.addRegion(), /*arguments=*/llvm::None, /*argTypes=*/llvm::None);
+    if (parseResult.hasValue() && mlir::failed(*parseResult))
+      return mlir::failure();
   }
 
-  return success();
+  return mlir::success();
 }
 
 void fir::GlobalOp::appendInitialValue(mlir::Operation *op) {
@@ -680,21 +694,89 @@
 
 mlir::ParseResult fir::GlobalOp::verifyValidLinkage(StringRef linkage) {
   // Supporting only a subset of the LLVM linkage types for now
-  static const llvm::SmallVector<const char *, 3> validNames = {
-      "internal", "common", "weak"};
+  static const char *validNames[] = {"common", "internal", "linkonce", "weak"};
   return mlir::success(llvm::is_contained(validNames, linkage));
 }
 
 //===----------------------------------------------------------------------===//
+// InsertValueOp
+//===----------------------------------------------------------------------===//
+
+static bool checkIsIntegerConstant(mlir::Value v, int64_t conVal) {
+  if (auto c = dyn_cast_or_null<mlir::ConstantOp>(v.getDefiningOp())) {
+    auto attr = c.getValue();
+    if (auto iattr = attr.dyn_cast<mlir::IntegerAttr>())
+      return iattr.getInt() == conVal;
+  }
+  return false;
+}
+static bool isZero(mlir::Value v) { return checkIsIntegerConstant(v, 0); }
+static bool isOne(mlir::Value v) { return checkIsIntegerConstant(v, 1); }
+
+// Undo some complex patterns created in the front-end and turn them back into
+// complex ops.
+template <typename FltOp, typename CpxOp>
+struct UndoComplexPattern : public mlir::RewritePattern {
+  UndoComplexPattern(mlir::MLIRContext *ctx)
+      : mlir::RewritePattern("fir.insert_value", {}, 2, ctx) {}
+
+  mlir::LogicalResult
+  matchAndRewrite(mlir::Operation *op,
+                  mlir::PatternRewriter &rewriter) const override {
+    auto insval = dyn_cast_or_null<fir::InsertValueOp>(op);
+    if (!insval || !insval.getType().isa<fir::ComplexType>())
+      return mlir::failure();
+    auto insval2 =
+        dyn_cast_or_null<fir::InsertValueOp>(insval.adt().getDefiningOp());
+    if (!insval2 || !isa<fir::UndefOp>(insval2.adt().getDefiningOp()))
+      return mlir::failure();
+    auto binf = dyn_cast_or_null<FltOp>(insval.val().getDefiningOp());
+    auto binf2 = dyn_cast_or_null<FltOp>(insval2.val().getDefiningOp());
+    if (!binf || !binf2 || insval.coor().size() != 1 ||
+        !isOne(insval.coor()[0]) || insval2.coor().size() != 1 ||
+        !isZero(insval2.coor()[0]))
+      return mlir::failure();
+    auto eai =
+        dyn_cast_or_null<fir::ExtractValueOp>(binf.lhs().getDefiningOp());
+    auto ebi =
+        dyn_cast_or_null<fir::ExtractValueOp>(binf.rhs().getDefiningOp());
+    auto ear =
+        dyn_cast_or_null<fir::ExtractValueOp>(binf2.lhs().getDefiningOp());
+    auto ebr =
+        dyn_cast_or_null<fir::ExtractValueOp>(binf2.rhs().getDefiningOp());
+    if (!eai || !ebi || !ear || !ebr || ear.adt() != eai.adt() ||
+        ebr.adt() != ebi.adt() || eai.coor().size() != 1 ||
+        !isOne(eai.coor()[0]) || ebi.coor().size() != 1 ||
+        !isOne(ebi.coor()[0]) || ear.coor().size() != 1 ||
+        !isZero(ear.coor()[0]) || ebr.coor().size() != 1 ||
+        !isZero(ebr.coor()[0]))
+      return mlir::failure();
+    rewriter.replaceOpWithNewOp<CpxOp>(op, ear.adt(), ebr.adt());
+    return mlir::success();
+  }
+};
+
+void fir::InsertValueOp::getCanonicalizationPatterns(
+    mlir::OwningRewritePatternList &results, mlir::MLIRContext *context) {
+  results.insert<UndoComplexPattern<fir::AddfOp, fir::AddcOp>,
+                 UndoComplexPattern<fir::SubfOp, fir::SubcOp>>(context);
+}
+
+//===----------------------------------------------------------------------===//
 // IterWhileOp
 //===----------------------------------------------------------------------===//
 
 void fir::IterWhileOp::build(mlir::OpBuilder &builder,
                              mlir::OperationState &result, mlir::Value lb,
                              mlir::Value ub, mlir::Value step,
-                             mlir::Value iterate, mlir::ValueRange iterArgs,
+                             mlir::Value iterate, bool finalCountValue,
+                             mlir::ValueRange iterArgs,
                              llvm::ArrayRef<mlir::NamedAttribute> attributes) {
   result.addOperands({lb, ub, step, iterate});
+  if (finalCountValue) {
+    result.addTypes(builder.getIndexType());
+    result.addAttribute(finalValueAttrName(), builder.getUnitAttr());
+  }
   result.addTypes(iterate.getType());
   result.addOperands(iterArgs);
   for (auto v : iterArgs)
@@ -736,24 +818,50 @@
 
   // Parse the initial iteration arguments.
   llvm::SmallVector<mlir::OpAsmParser::OperandType, 4> regionArgs;
+  auto prependCount = false;
+
   // Induction variable.
   regionArgs.push_back(inductionVariable);
   regionArgs.push_back(iterateVar);
-  result.addTypes(i1Type);
 
-  if (mlir::succeeded(parser.parseOptionalKeyword("iter_args"))) {
+  if (succeeded(parser.parseOptionalKeyword("iter_args"))) {
     llvm::SmallVector<mlir::OpAsmParser::OperandType, 4> operands;
     llvm::SmallVector<mlir::Type, 4> regionTypes;
     // Parse assignment list and results type list.
     if (parser.parseAssignmentList(regionArgs, operands) ||
         parser.parseArrowTypeList(regionTypes))
-      return mlir::failure();
+      return failure();
+    if (regionTypes.size() == operands.size() + 2)
+      prependCount = true;
+    llvm::ArrayRef<mlir::Type> resTypes = regionTypes;
+    resTypes = prependCount ? resTypes.drop_front(2) : resTypes;
     // Resolve input operands.
-    for (auto operand_type : llvm::zip(operands, regionTypes))
+    for (auto operand_type : llvm::zip(operands, resTypes))
       if (parser.resolveOperand(std::get<0>(operand_type),
                                 std::get<1>(operand_type), result.operands))
-        return mlir::failure();
-    result.addTypes(regionTypes);
+        return failure();
+    if (prependCount) {
+      // This is an assert here, because these types are verified.
+      assert(regionTypes[0].isa<mlir::IndexType>() &&
+             regionTypes[1].isSignlessInteger(1));
+      result.addTypes(regionTypes);
+    } else {
+      result.addTypes(i1Type);
+      result.addTypes(resTypes);
+    }
+  } else if (succeeded(parser.parseOptionalArrow())) {
+    llvm::SmallVector<mlir::Type, 4> typeList;
+    if (parser.parseLParen() || parser.parseTypeList(typeList) ||
+        parser.parseRParen())
+      return failure();
+    // Type list must be "(index, i1)".
+    if (typeList.size() != 2 || !typeList[0].isa<mlir::IndexType>() ||
+        !typeList[1].isSignlessInteger(1))
+      return failure();
+    result.addTypes(typeList);
+    prependCount = true;
+  } else {
+    result.addTypes(i1Type);
   }
 
   if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
@@ -761,7 +869,11 @@
 
   llvm::SmallVector<mlir::Type, 4> argTypes;
   // Induction variable (hidden)
-  argTypes.push_back(indexType);
+  if (prependCount)
+    result.addAttribute(IterWhileOp::finalValueAttrName(),
+                        builder.getUnitAttr());
+  else
+    argTypes.push_back(indexType);
   // Loop carried variables (including iterate)
   argTypes.append(result.types.begin(), result.types.end());
   // Parse the body region.
@@ -780,10 +892,6 @@
 }
 
 static mlir::LogicalResult verify(fir::IterWhileOp op) {
-  if (auto cst = dyn_cast_or_null<ConstantIndexOp>(op.step().getDefiningOp()))
-    if (cst.getValue() <= 0)
-      return op.emitOpError("constant step operand must be positive");
-
   // Check that the body defines as single block argument for the induction
   // variable.
   auto *body = op.getBody();
@@ -797,6 +905,19 @@
         "the induction variable");
 
   auto opNumResults = op.getNumResults();
+  if (op.finalValue()) {
+    // Result type must be "(index, i1, ...)".
+    if (!op.getResult(0).getType().isa<mlir::IndexType>())
+      return op.emitOpError("result #0 expected to be index");
+    if (!op.getResult(1).getType().isSignlessInteger(1))
+      return op.emitOpError("result #1 expected to be i1");
+    opNumResults--;
+  } else {
+    // iterate_while always returns the early exit induction value.
+    // Result type must be "(i1, ...)"
+    if (!op.getResult(0).getType().isSignlessInteger(1))
+      return op.emitOpError("result #0 expected to be i1");
+  }
   if (opNumResults == 0)
     return mlir::failure();
   if (op.getNumIterOperands() != opNumResults)
@@ -807,7 +928,8 @@
         "mismatch in number of basic block args and defined values");
   auto iterOperands = op.getIterOperands();
   auto iterArgs = op.getRegionIterArgs();
-  auto opResults = op.getResults();
+  auto opResults =
+      op.finalValue() ? op.getResults().drop_front() : op.getResults();
   unsigned i = 0;
   for (auto e : llvm::zip(iterOperands, iterArgs, opResults)) {
     if (std::get<0>(e).getType() != std::get<2>(e).getType())
@@ -835,9 +957,14 @@
     llvm::interleaveComma(
         llvm::zip(regionArgs.drop_front(), operands.drop_front()), p,
         [&](auto it) { p << std::get<0>(it) << " = " << std::get<1>(it); });
-    p << ") -> (" << op.getResultTypes().drop_front() << ')';
+    auto resTypes = op.finalValue() ? op.getResultTypes()
+                                    : op.getResultTypes().drop_front();
+    p << ") -> (" << resTypes << ')';
+  } else if (op.finalValue()) {
+    p << " -> (" << op.getResultTypes() << ')';
   }
-  p.printOptionalAttrDictWithKeyword(op->getAttrs(), {});
+  p.printOptionalAttrDictWithKeyword(op->getAttrs(),
+                                     {IterWhileOp::finalValueAttrName()});
   p.printRegion(op.region(), /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/true);
 }
@@ -855,6 +982,27 @@
   return success();
 }
 
+mlir::BlockArgument fir::IterWhileOp::iterArgToBlockArg(mlir::Value iterArg) {
+  for (auto i : llvm::enumerate(initArgs()))
+    if (iterArg == i.value())
+      return region().front().getArgument(i.index() + 1);
+  return {};
+}
+
+void fir::IterWhileOp::resultToSourceOps(
+    llvm::SmallVectorImpl<mlir::Value> &results, unsigned resultNum) {
+  auto oper = finalValue() ? resultNum + 1 : resultNum;
+  auto *term = region().front().getTerminator();
+  if (oper < term->getNumOperands())
+    results.push_back(term->getOperand(oper));
+}
+
+mlir::Value fir::IterWhileOp::blockArgToSourceOp(unsigned blockArgNum) {
+  if (blockArgNum > 0 && blockArgNum <= initArgs().size())
+    return initArgs()[blockArgNum - 1];
+  return {};
+}
+
 //===----------------------------------------------------------------------===//
 // LoadOp
 //===----------------------------------------------------------------------===//
@@ -880,15 +1028,19 @@
 void fir::DoLoopOp::build(mlir::OpBuilder &builder,
                           mlir::OperationState &result, mlir::Value lb,
                           mlir::Value ub, mlir::Value step, bool unordered,
-                          mlir::ValueRange iterArgs,
+                          bool finalCountValue, mlir::ValueRange iterArgs,
                           llvm::ArrayRef<mlir::NamedAttribute> attributes) {
   result.addOperands({lb, ub, step});
   result.addOperands(iterArgs);
+  if (finalCountValue) {
+    result.addTypes(builder.getIndexType());
+    result.addAttribute(finalValueAttrName(), builder.getUnitAttr());
+  }
   for (auto v : iterArgs)
     result.addTypes(v.getType());
   mlir::Region *bodyRegion = result.addRegion();
   bodyRegion->push_back(new Block{});
-  if (iterArgs.empty())
+  if (iterArgs.empty() && !finalCountValue)
     DoLoopOp::ensureTerminator(*bodyRegion, builder, result.location);
   bodyRegion->front().addArgument(builder.getIndexType());
   bodyRegion->front().addArguments(iterArgs.getTypes());
@@ -922,6 +1074,7 @@
   // Parse the optional initial iteration arguments.
   llvm::SmallVector<mlir::OpAsmParser::OperandType, 4> regionArgs, operands;
   llvm::SmallVector<mlir::Type, 4> argTypes;
+  auto prependCount = false;
   regionArgs.push_back(inductionVariable);
 
   if (succeeded(parser.parseOptionalKeyword("iter_args"))) {
@@ -929,18 +1082,30 @@
     if (parser.parseAssignmentList(regionArgs, operands) ||
         parser.parseArrowTypeList(result.types))
       return failure();
+    if (result.types.size() == operands.size() + 1)
+      prependCount = true;
     // Resolve input operands.
-    for (auto operand_type : llvm::zip(operands, result.types))
+    llvm::ArrayRef<mlir::Type> resTypes = result.types;
+    for (auto operand_type :
+         llvm::zip(operands, prependCount ? resTypes.drop_front() : resTypes))
       if (parser.resolveOperand(std::get<0>(operand_type),
                                 std::get<1>(operand_type), result.operands))
         return failure();
+  } else if (succeeded(parser.parseOptionalArrow())) {
+    if (parser.parseKeyword("index"))
+      return failure();
+    result.types.push_back(indexType);
+    prependCount = true;
   }
 
   if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
     return mlir::failure();
 
   // Induction variable.
-  argTypes.push_back(indexType);
+  if (prependCount)
+    result.addAttribute(DoLoopOp::finalValueAttrName(), builder.getUnitAttr());
+  else
+    argTypes.push_back(indexType);
   // Loop carried variables
   argTypes.append(result.types.begin(), result.types.end());
   // Parse the body region.
@@ -953,7 +1118,7 @@
   if (parser.parseRegion(*body, regionArgs, argTypes))
     return failure();
 
-  fir::DoLoopOp::ensureTerminator(*body, builder, result.location);
+  DoLoopOp::ensureTerminator(*body, builder, result.location);
 
   return mlir::success();
 }
@@ -969,10 +1134,6 @@
 
 // Lifted from loop.loop
 static mlir::LogicalResult verify(fir::DoLoopOp op) {
-  if (auto cst = dyn_cast_or_null<ConstantIndexOp>(op.step().getDefiningOp()))
-    if (cst.getValue() <= 0)
-      return op.emitOpError("constant step operand must be positive");
-
   // Check that the body defines as single block argument for the induction
   // variable.
   auto *body = op.getBody();
@@ -984,6 +1145,12 @@
   auto opNumResults = op.getNumResults();
   if (opNumResults == 0)
     return success();
+
+  if (op.finalValue()) {
+    if (op.unordered())
+      return op.emitOpError("unordered loop has no final value");
+    opNumResults--;
+  }
   if (op.getNumIterOperands() != opNumResults)
     return op.emitOpError(
         "mismatch in number of loop-carried values and defined values");
@@ -992,7 +1159,8 @@
         "mismatch in number of basic block args and defined values");
   auto iterOperands = op.getIterOperands();
   auto iterArgs = op.getRegionIterArgs();
-  auto opResults = op.getResults();
+  auto opResults =
+      op.finalValue() ? op.getResults().drop_front() : op.getResults();
   unsigned i = 0;
   for (auto e : llvm::zip(iterOperands, iterArgs, opResults)) {
     if (std::get<0>(e).getType() != std::get<2>(e).getType())
@@ -1022,9 +1190,13 @@
     });
     p << ") -> (" << op.getResultTypes() << ')';
     printBlockTerminators = true;
+  } else if (op.finalValue()) {
+    p << " -> " << op.getResultTypes();
+    printBlockTerminators = true;
   }
   p.printOptionalAttrDictWithKeyword(op->getAttrs(),
-                                     {fir::DoLoopOp::unorderedAttrName()});
+                                     {fir::DoLoopOp::unorderedAttrName(),
+                                      fir::DoLoopOp::finalValueAttrName()});
   p.printRegion(op.region(), /*printEntryBlockArgs=*/false,
                 printBlockTerminators);
 }
@@ -1042,6 +1214,33 @@
   return success();
 }
 
+/// Translate a value passed as an iter_arg to the corresponding block
+/// argument in the body of the loop.
+mlir::BlockArgument fir::DoLoopOp::iterArgToBlockArg(mlir::Value iterArg) {
+  for (auto i : llvm::enumerate(initArgs()))
+    if (iterArg == i.value())
+      return region().front().getArgument(i.index() + 1);
+  return {};
+}
+
+/// Translate the result vector (by index number) to the corresponding value
+/// to the `fir.result` Op.
+void fir::DoLoopOp::resultToSourceOps(
+    llvm::SmallVectorImpl<mlir::Value> &results, unsigned resultNum) {
+  auto oper = finalValue() ? resultNum + 1 : resultNum;
+  auto *term = region().front().getTerminator();
+  if (oper < term->getNumOperands())
+    results.push_back(term->getOperand(oper));
+}
+
+/// Translate the block argument (by index number) to the corresponding value
+/// passed as an iter_arg to the parent DoLoopOp.
+mlir::Value fir::DoLoopOp::blockArgToSourceOp(unsigned blockArgNum) {
+  if (blockArgNum > 0 && blockArgNum <= initArgs().size())
+    return initArgs()[blockArgNum - 1];
+  return {};
+}
+
 //===----------------------------------------------------------------------===//
 // MulfOp
 //===----------------------------------------------------------------------===//
@@ -1052,6 +1251,89 @@
 }
 
 //===----------------------------------------------------------------------===//
+// ReboxOp
+//===----------------------------------------------------------------------===//
+
+/// Get the scalar type related to a fir.box type.
+/// Example: return f32 for !fir.box<!fir.heap<!fir.array<?x?xf32>>.
+static mlir::Type getBoxScalarEleTy(mlir::Type boxTy) {
+  auto eleTy = fir::dyn_cast_ptrOrBoxEleTy(boxTy);
+  if (auto seqTy = eleTy.dyn_cast<fir::SequenceType>())
+    return seqTy.getEleTy();
+  return eleTy;
+}
+
+/// Get the rank from a !fir.box type
+static unsigned getBoxRank(mlir::Type boxTy) {
+  auto eleTy = fir::dyn_cast_ptrOrBoxEleTy(boxTy);
+  if (auto seqTy = eleTy.dyn_cast<fir::SequenceType>())
+    return seqTy.getDimension();
+  return 0;
+}
+
+static mlir::LogicalResult verify(fir::ReboxOp op) {
+  auto inputBoxTy = op.box().getType();
+  if (fir::isa_unknown_size_box(inputBoxTy))
+    return op.emitOpError("box operand must not have unknown rank or type");
+  auto outBoxTy = op.getType();
+  if (fir::isa_unknown_size_box(outBoxTy))
+    return op.emitOpError("result type must not have unknown rank or type");
+  auto inputRank = getBoxRank(inputBoxTy);
+  auto inputEleTy = getBoxScalarEleTy(inputBoxTy);
+  auto outRank = getBoxRank(outBoxTy);
+  auto outEleTy = getBoxScalarEleTy(outBoxTy);
+
+  if (auto slice = op.slice()) {
+    // Slicing case
+    if (slice.getType().cast<fir::SliceType>().getRank() != inputRank)
+      return op.emitOpError("slice operand rank must match box operand rank");
+    if (auto shape = op.shape()) {
+      if (auto shiftTy = shape.getType().dyn_cast<fir::ShiftType>()) {
+        if (shiftTy.getRank() != inputRank)
+          return op.emitOpError("shape operand and input box ranks must match "
+                                "when there is a slice");
+      } else {
+        return op.emitOpError("shape operand must absent or be a fir.shift "
+                              "when there is a slice");
+      }
+    }
+    if (auto sliceOp = slice.getDefiningOp()) {
+      auto slicedRank = mlir::cast<fir::SliceOp>(sliceOp).getOutRank();
+      if (slicedRank != outRank)
+        return op.emitOpError("result type rank and rank after applying slice "
+                              "operand must match");
+    }
+  } else {
+    // Reshaping case
+    unsigned shapeRank = inputRank;
+    if (auto shape = op.shape()) {
+      auto ty = shape.getType();
+      if (auto shapeTy = ty.dyn_cast<fir::ShapeType>()) {
+        shapeRank = shapeTy.getRank();
+      } else if (auto shapeShiftTy = ty.dyn_cast<fir::ShapeShiftType>()) {
+        shapeRank = shapeShiftTy.getRank();
+      } else {
+        auto shiftTy = ty.cast<fir::ShiftType>();
+        shapeRank = shiftTy.getRank();
+        if (shapeRank != inputRank)
+          return op.emitOpError("shape operand and input box ranks must match "
+                                "when the shape is a fir.shift");
+      }
+    }
+    if (shapeRank != outRank)
+      return op.emitOpError("result type and shape operand ranks must match");
+  }
+
+  if (inputEleTy != outEleTy)
+    // TODO: check that outBoxTy is a parent type of inputBoxTy for derived
+    // types.
+    if (!inputEleTy.isa<fir::RecordType>())
+      return op.emitOpError(
+          "op input and output element types must match for intrinsic types");
+  return mlir::success();
+}
+
+//===----------------------------------------------------------------------===//
 // ResultOp
 //===----------------------------------------------------------------------===//
 
@@ -1084,7 +1366,7 @@
 template <typename A, typename... AdditionalArgs>
 static A getSubOperands(unsigned pos, A allArgs,
                         mlir::DenseIntElementsAttr ranges,
-                        AdditionalArgs &&...additionalArgs) {
+                        AdditionalArgs &&... additionalArgs) {
   unsigned start = 0;
   for (unsigned i = 0; i < pos; ++i)
     start += (*(ranges.begin() + i)).getZExtValue();
@@ -1221,7 +1503,7 @@
       return mlir::failure();
     dests.push_back(dest);
     destArgs.push_back(destArg);
-    if (!parser.parseOptionalRSquare())
+    if (mlir::succeeded(parser.parseOptionalRSquare()))
       break;
     if (parser.parseComma())
       return mlir::failure();
@@ -1422,7 +1704,7 @@
     attrs.push_back(attr);
     dests.push_back(dest);
     destArgs.push_back(destArg);
-    if (!parser.parseOptionalRSquare())
+    if (mlir::succeeded(parser.parseOptionalRSquare()))
       break;
     if (parser.parseComma())
       return mlir::failure();
@@ -1452,6 +1734,25 @@
 }
 
 //===----------------------------------------------------------------------===//
+// SliceOp
+//===----------------------------------------------------------------------===//
+
+/// Return the output rank of a slice op. The output rank must be between 1 and
+/// the rank of the array being sliced (inclusive).
+unsigned fir::SliceOp::getOutputRank(mlir::ValueRange triples) {
+  unsigned rank = 0;
+  if (!triples.empty()) {
+    for (unsigned i = 1, end = triples.size(); i < end; i += 3) {
+      auto op = triples[i].getDefiningOp();
+      if (!mlir::isa_and_nonnull<fir::UndefOp>(op))
+        ++rank;
+    }
+    assert(rank > 0);
+  }
+  return rank;
+}
+
+//===----------------------------------------------------------------------===//
 // StoreOp
 //===----------------------------------------------------------------------===//
 
@@ -1486,6 +1787,7 @@
 //===----------------------------------------------------------------------===//
 // IfOp
 //===----------------------------------------------------------------------===//
+
 void fir::IfOp::build(mlir::OpBuilder &builder, OperationState &result,
                       mlir::Value cond, bool withElseRegion) {
   build(builder, result, llvm::None, cond, withElseRegion);
@@ -1500,13 +1802,13 @@
   mlir::Region *thenRegion = result.addRegion();
   thenRegion->push_back(new mlir::Block());
   if (resultTypes.empty())
-    fir::IfOp::ensureTerminator(*thenRegion, builder, result.location);
+    IfOp::ensureTerminator(*thenRegion, builder, result.location);
 
   mlir::Region *elseRegion = result.addRegion();
   if (withElseRegion) {
     elseRegion->push_back(new mlir::Block());
     if (resultTypes.empty())
-      fir::IfOp::ensureTerminator(*elseRegion, builder, result.location);
+      IfOp::ensureTerminator(*elseRegion, builder, result.location);
   }
 }
 
@@ -1523,28 +1825,27 @@
       parser.resolveOperand(cond, i1Type, result.operands))
     return mlir::failure();
 
-  if (parser.parseRegion(*thenRegion, {}, {}))
+  if (parser.parseOptionalArrowTypeList(result.types))
     return mlir::failure();
 
-  fir::IfOp::ensureTerminator(*thenRegion, parser.getBuilder(),
-                              result.location);
+  if (parser.parseRegion(*thenRegion, {}, {}))
+    return mlir::failure();
+  IfOp::ensureTerminator(*thenRegion, parser.getBuilder(), result.location);
 
-  if (!parser.parseOptionalKeyword("else")) {
+  if (mlir::succeeded(parser.parseOptionalKeyword("else"))) {
     if (parser.parseRegion(*elseRegion, {}, {}))
       return mlir::failure();
-    fir::IfOp::ensureTerminator(*elseRegion, parser.getBuilder(),
-                                result.location);
+    IfOp::ensureTerminator(*elseRegion, parser.getBuilder(), result.location);
   }
 
   // Parse the optional attribute list.
   if (parser.parseOptionalAttrDict(result.attributes))
     return mlir::failure();
-
   return mlir::success();
 }
 
 static LogicalResult verify(fir::IfOp op) {
-  if (op.getNumResults() != 0 && op.otherRegion().empty())
+  if (op.getNumResults() != 0 && op.elseRegion().empty())
     return op.emitOpError("must have an else block if defining values");
 
   return mlir::success();
@@ -1557,11 +1858,11 @@
     p << " -> (" << op.getResultTypes() << ')';
     printBlockTerminators = true;
   }
-  p.printRegion(op.whereRegion(), /*printEntryBlockArgs=*/false,
+  p.printRegion(op.thenRegion(), /*printEntryBlockArgs=*/false,
                 printBlockTerminators);
 
   // Print the 'else' regions if it exists and has a block.
-  auto &otherReg = op.otherRegion();
+  auto &otherReg = op.elseRegion();
   if (!otherReg.empty()) {
     p << " else";
     p.printRegion(otherReg, /*printEntryBlockArgs=*/false,
@@ -1570,6 +1871,16 @@
   p.printOptionalAttrDict(op->getAttrs());
 }
 
+void fir::IfOp::resultToSourceOps(llvm::SmallVectorImpl<mlir::Value> &results,
+                                  unsigned resultNum) {
+  auto *term = thenRegion().front().getTerminator();
+  if (resultNum < term->getNumOperands())
+    results.push_back(term->getOperand(resultNum));
+  term = elseRegion().front().getTerminator();
+  if (resultNum < term->getNumOperands())
+    results.push_back(term->getOperand(resultNum));
+}
+
 //===----------------------------------------------------------------------===//
 
 mlir::ParseResult fir::isValidCaseAttr(mlir::Attribute attr) {
@@ -1639,7 +1950,9 @@
     return f;
   mlir::OpBuilder modBuilder(module.getBodyRegion());
   modBuilder.setInsertionPoint(module.getBody()->getTerminator());
-  return modBuilder.create<mlir::FuncOp>(loc, name, type, attrs);
+  auto result = modBuilder.create<mlir::FuncOp>(loc, name, type, attrs);
+  result.setVisibility(mlir::SymbolTable::Visibility::Private);
+  return result;
 }
 
 fir::GlobalOp fir::createGlobalOp(mlir::Location loc, mlir::ModuleOp module,
@@ -1648,7 +1961,9 @@
   if (auto g = module.lookupSymbol<fir::GlobalOp>(name))
     return g;
   mlir::OpBuilder modBuilder(module.getBodyRegion());
-  return modBuilder.create<fir::GlobalOp>(loc, name, type, attrs);
+  auto result = modBuilder.create<fir::GlobalOp>(loc, name, type, attrs);
+  result.setVisibility(mlir::SymbolTable::Visibility::Private);
+  return result;
 }
 
 // Tablegen operators
diff --git a/test/Fir/fir-ops.fir b/test/Fir/fir-ops.fir
index cbfe318..6b76025 100644
--- a/test/Fir/fir-ops.fir
+++ b/test/Fir/fir-ops.fir
@@ -1,5 +1,5 @@
 // Test the FIR operations
-// Parse operations and check that we can reparse what we print.
+
 // RUN: fir-opt %s | fir-opt | FileCheck %s
 
 // CHECK-LABEL: func private @it1() -> !fir.int<4>
@@ -97,10 +97,12 @@
   %23 = fir.extract_value %22, %21 : (!fir.type<derived{f:f32}>, !fir.field) -> f32
 
 // CHECK: [[VAL_26:%.*]] = constant 1 : i32
+// CHECK: [[VAL_27:%.*]] = fir.shape [[VAL_21]] : (i32) -> !fir.shape<1>
 // CHECK: [[VAL_28:%.*]] = constant 1.0
 // CHECK: [[VAL_29:%.*]] = fir.insert_value [[VAL_24]], [[VAL_28]], [[VAL_23]] : (!fir.type<derived{f:f32}>, f32, !fir.field) -> !fir.type<derived{f:f32}>
 // CHECK: [[VAL_30:%.*]] = fir.len_param_index f, !fir.type<derived3{f:f32}>
   %c1 = constant 1 : i32
+  %24 = fir.shape %19 : (i32) -> !fir.shape<1>
   %cf1 = constant 1.0 : f32
   %25 = fir.insert_value %22, %cf1, %21 : (!fir.type<derived{f:f32}>, f32, !fir.field) -> !fir.type<derived{f:f32}>
   %26 = fir.len_param_index f, !fir.type<derived3{f:f32}>
@@ -539,6 +541,7 @@
 // CHECK: [[VAL_175:%.*]] = fir.subf [[VAL_174]], [[VAL_170]] : !fir.real<16>
 // CHECK: [[VAL_176:%.*]] = fir.mulf [[VAL_173]], [[VAL_175]] : !fir.real<16>
 // CHECK: [[VAL_177:%.*]] = fir.divf [[VAL_176]], [[VAL_169]] : !fir.real<16>
+// CHECK: [[VAL_178:%.*]] = fir.modf [[VAL_177]], [[VAL_170]] : !fir.real<16>
   %c1 = constant 1.0 : f32
   %0 = fir.convert %c1 : (f32) -> !fir.real<16>
   %1 = fir.negf %a : !fir.real<16>
@@ -546,35 +549,36 @@
   %3 = fir.subf %2, %b : !fir.real<16>
   %4 = fir.mulf %1, %3 : !fir.real<16>
   %5 = fir.divf %4, %a : !fir.real<16>
-// CHECK: return [[VAL_177]] : !fir.real<16>
+  %6 = fir.modf %5, %b : !fir.real<16>
+// CHECK: return [[VAL_178]] : !fir.real<16>
 // CHECK: }
-  return %5 : !fir.real<16>
+  return %6 : !fir.real<16>
 }
 
 // CHECK-LABEL: func @arith_complex(
-// CHECK-SAME: [[VAL_178:%.*]]: !fir.complex<16>, [[VAL_179:%.*]]: !fir.complex<16>) -> !fir.complex<16> {
+// CHECK-SAME: [[VAL_179:%.*]]: !fir.complex<16>, [[VAL_180:%.*]]: !fir.complex<16>) -> !fir.complex<16> {
 func @arith_complex(%a : !fir.complex<16>, %b : !fir.complex<16>) -> !fir.complex<16> {
-// CHECK: [[VAL_180:%.*]] = fir.negc [[VAL_178]] : !fir.complex<16>
-// CHECK: [[VAL_181:%.*]] = fir.addc [[VAL_179]], [[VAL_180]] : !fir.complex<16>
-// CHECK: [[VAL_182:%.*]] = fir.subc [[VAL_181]], [[VAL_179]] : !fir.complex<16>
-// CHECK: [[VAL_183:%.*]] = fir.mulc [[VAL_180]], [[VAL_182]] : !fir.complex<16>
-// CHECK: [[VAL_184:%.*]] = fir.divc [[VAL_183]], [[VAL_178]] : !fir.complex<16>
+// CHECK: [[VAL_181:%.*]] = fir.negc [[VAL_179]] : !fir.complex<16>
+// CHECK: [[VAL_182:%.*]] = fir.addc [[VAL_180]], [[VAL_181]] : !fir.complex<16>
+// CHECK: [[VAL_183:%.*]] = fir.subc [[VAL_182]], [[VAL_180]] : !fir.complex<16>
+// CHECK: [[VAL_184:%.*]] = fir.mulc [[VAL_181]], [[VAL_183]] : !fir.complex<16>
+// CHECK: [[VAL_185:%.*]] = fir.divc [[VAL_184]], [[VAL_179]] : !fir.complex<16>
   %1 = fir.negc %a : !fir.complex<16>
   %2 = fir.addc %b, %1 : !fir.complex<16>
   %3 = fir.subc %2, %b : !fir.complex<16>
   %4 = fir.mulc %1, %3 : !fir.complex<16>
   %5 = fir.divc %4, %a : !fir.complex<16>
-// CHECK: return [[VAL_184]] : !fir.complex<16>
+// CHECK: return [[VAL_185]] : !fir.complex<16>
 // CHECK: }
   return %5 : !fir.complex<16>
 }
 
-// CHECK-LABEL: func @character_literal() -> !fir.array<13x!fir.char<1>> {
-func @character_literal() -> !fir.array<13 x !fir.char<1>> {
-// CHECK: [[VAL_185:%.*]] = fir.string_lit "Hello, World!"(13) : !fir.char<1>
-  %0 = fir.string_lit "Hello, World!"(13) : !fir.char<1>
-// CHECK: return [[VAL_185]] : !fir.array<13x!fir.char<1>>
-  return %0 : !fir.array<13 x !fir.char<1>>
+// CHECK-LABEL: func @character_literal() -> !fir.char<1,13> {
+func @character_literal() -> !fir.char<1,13> {
+// CHECK: [[VAL_186:%.*]] = fir.string_lit "Hello, World!"(13) : !fir.char<1,13>
+  %0 = fir.string_lit "Hello, World!"(13) : !fir.char<1,13>
+// CHECK: return [[VAL_186]] : !fir.char<1,13>
+  return %0 : !fir.char<1,13>
 // CHECK: }
 }
 
@@ -582,14 +586,14 @@
 func private @earlyexit2(%a : i32) -> i1
 
 // CHECK-LABEL: func @early_exit(
-// CHECK-SAME: [[VAL_186:%.*]]: i1, [[VAL_187:%.*]]: i32) -> i1 {
+// CHECK-SAME: [[VAL_187:%.*]]: i1, [[VAL_188:%.*]]: i32) -> i1 {
 func @early_exit(%ok : i1, %k : i32) -> i1 {
-// CHECK: [[VAL_188:%.*]] = constant 1 : index
-// CHECK: [[VAL_189:%.*]] = constant 100 : index
+// CHECK: [[VAL_189:%.*]] = constant 1 : index
+// CHECK: [[VAL_190:%.*]] = constant 100 : index
   %c1 = constant 1 : index
   %c100 = constant 100 : index
 
-// CHECK: [[VAL_190:%.*]], [[VAL_191:%.*]] = fir.iterate_while ([[VAL_192:%.*]] = [[VAL_188]] to [[VAL_189]] step [[VAL_188]]) and ([[VAL_193:%.*]] = [[VAL_186]]) iter_args([[VAL_194:%.*]] = [[VAL_187]]) -> (i32) {
+// CHECK: %[[VAL_191:.*]]:2 = fir.iterate_while ([[VAL_192:%.*]] = [[VAL_189]] to [[VAL_190]] step [[VAL_189]]) and ([[VAL_193:%.*]] = [[VAL_187]]) iter_args([[VAL_194:%.*]] = [[VAL_188]]) -> (i32) {
 // CHECK: [[VAL_195:%.*]] = call @earlyexit2([[VAL_194]]) : (i32) -> i1
 // CHECK: fir.result [[VAL_195]], [[VAL_194]] : i1, i32
 // CHECK: }
@@ -597,11 +601,56 @@
     %stop = call @earlyexit2(%v) : (i32) -> i1
     fir.result %stop, %v : i1, i32
   }
-// CHECK: return [[VAL_190]] : i1
+// CHECK: return %[[VAL_191]]#0 : i1
 // CHECK: }
   return %newOk#0 : i1
 }
 
+// CHECK-LABEL: @array_access
+func @array_access(%arr : !fir.ref<!fir.array<?x?xf32>>) {
+  // CHECK-DAG: %[[c1:.*]] = constant 100
+  // CHECK-DAG: %[[c2:.*]] = constant 50
+  %c100 = constant 100 : index
+  %c50 = constant 50 : index
+  // CHECK: %[[sh:.*]] = fir.shape %[[c1]], %[[c2]] : {{.*}} -> !fir.shape<2>
+  %shape = fir.shape %c100, %c50 : (index, index) -> !fir.shape<2>
+  %c47 = constant 47 : index
+  %c78 = constant 78 : index
+  %c3 = constant 3 : index
+  %c18 = constant 18 : index
+  %c36 = constant 36 : index
+  %c4 = constant 4 : index
+  // CHECK: %[[sl:.*]] = fir.slice {{.*}} -> !fir.slice<2>
+  %slice = fir.slice %c47, %c78, %c3, %c18, %c36, %c4 : (index,index,index,index,index,index) -> !fir.slice<2>
+  %c0 = constant 0 : index
+  %c99 = constant 99 : index
+  %c1 = constant 1 : index
+  fir.do_loop %i = %c0 to %c99 step %c1 {
+    %c49 = constant 49 : index
+    fir.do_loop %j = %c0 to %c49 step %c1 {
+      // CHECK: fir.array_coor %{{.*}}(%[[sh]]) [%[[sl]]] %{{.*}}, %{{.*}} :
+      %p = fir.array_coor %arr(%shape)[%slice] %i, %j : (!fir.ref<!fir.array<?x?xf32>>, !fir.shape<2>, !fir.slice<2>, index, index) -> !fir.ref<f32>
+      %x = constant 42.0 : f32
+      fir.store %x to %p : !fir.ref<f32>
+    }
+  }
+  return
+}
+
+// CHECK-LABEL: @test_is_present
+func @test_is_present(%arg0: !fir.box<!fir.array<?xf32>>) -> i1 {
+  // CHECK: fir.is_present %{{.*}} : (!fir.box<!fir.array<?xf32>>) -> i1
+  %0 = fir.is_present %arg0 : (!fir.box<!fir.array<?xf32>>) -> i1
+  return %0 : i1
+}
+// CHECK-LABEL: @test_absent
+func @test_absent() -> i1 {
+  // CHECK: fir.absent !fir.box<!fir.array<?xf32>>
+  %0 = fir.absent !fir.box<!fir.array<?xf32>>
+  %1 = fir.call @_QPfoo(%0) : (!fir.box<!fir.array<?xf32>>) -> i1
+  return %1 : i1
+}
+
 // CHECK-LABEL: @test_misc_ops(
 // CHECK-SAME: [[ARR1:%.*]]: !fir.ref<!fir.array<?x?xf32>>, [[INDXM:%.*]]: index, [[INDXN:%.*]]: index, [[INDXO:%.*]]: index, [[INDXP:%.*]]: index)
 func @test_misc_ops(%arr1 : !fir.ref<!fir.array<?x?xf32>>, %m : index, %n : index, %o : index, %p : index) {
@@ -617,7 +666,11 @@
   %c1_i32 = constant 9 : i32
 
   // CHECK: [[ARR2:%.*]] = fir.zero_bits !fir.array<10xi32>
+  // CHECK: [[ARR3:%.*]] = fir.insert_on_range [[ARR2]], [[C1_I32]], [[C2]], [[C9]] : (!fir.array<10xi32>, i32, index, index) -> !fir.array<10xi32>
+  // CHECK: fir.call @noret1([[ARR3]]) : (!fir.array<10xi32>) -> ()
   %arr2 = fir.zero_bits !fir.array<10xi32>
+  %arr3 = fir.insert_on_range %arr2, %c1_i32, %c2, %c9 : (!fir.array<10xi32>, i32, index, index) -> !fir.array<10xi32>
+  fir.call @noret1(%arr3) : (!fir.array<10xi32>) -> ()
 
   // CHECK: [[SHAPE:%.*]] = fir.shape_shift [[INDXM:%.*]], [[INDXN:%.*]], [[INDXO:%.*]], [[INDXP:%.*]] : (index, index, index, index) -> !fir.shapeshift<2>
   // CHECK: [[AV1:%.*]] = fir.array_load [[ARR1]]([[SHAPE]]) : (!fir.ref<!fir.array<?x?xf32>>, !fir.shapeshift<2>) -> !fir.array<?x?xf32>
@@ -632,3 +685,34 @@
 
   return
 }
+
+// CHECK-LABEL: @test_shift
+func @test_shift(%arg0: !fir.box<!fir.array<?xf32>>) -> !fir.ref<f32> {
+  %c4 = constant 4 : index
+  %c100 = constant 100 : index
+  // CHECK: fir.shift %{{.*}} : (index) -> !fir.shift<1>
+  %0 = fir.shift %c4 : (index) -> !fir.shift<1>
+  %1 = fir.array_coor %arg0(%0) %c100 : (!fir.box<!fir.array<?xf32>>, !fir.shift<1>, index) -> !fir.ref<f32>
+  return %1 : !fir.ref<f32>
+} 
+
+func private @bar_rebox_test(!fir.box<!fir.array<?x?xf32>>)
+// CHECK-LABEL: @test_rebox(
+func @test_rebox(%arg0: !fir.box<!fir.array<?xf32>>) {
+  %c0 = constant 0 : index
+  %c1 = constant 1 : index
+  %c2 = constant 2 : index
+  %c3 = constant 3 : index
+  %c4 = constant 4 : index
+  %c10 = constant 10 : index
+  %c33 = constant 33 : index
+  %0 = fir.slice %c10, %c33, %c2 : (index, index, index) -> !fir.slice<1>
+  %1 = fir.shift %c0 : (index) -> !fir.shift<1>
+  // CHECK: fir.rebox %{{.*}}(%{{.*}}) [%{{.*}}] : (!fir.box<!fir.array<?xf32>>, !fir.shift<1>, !fir.slice<1>) -> !fir.box<!fir.array<?xf32>>
+  %2 = fir.rebox %arg0(%1) [%0] : (!fir.box<!fir.array<?xf32>>, !fir.shift<1>, !fir.slice<1>) -> !fir.box<!fir.array<?xf32>>
+  %3 = fir.shape %c3, %c4 : (index, index) -> !fir.shape<2>
+  // CHECK: fir.rebox %{{.*}}(%{{.*}}) : (!fir.box<!fir.array<?xf32>>, !fir.shape<2>) -> !fir.box<!fir.array<?x?xf32>>
+  %4 = fir.rebox %2(%3) : (!fir.box<!fir.array<?xf32>>, !fir.shape<2>) -> !fir.box<!fir.array<?x?xf32>>
+  fir.call @bar_rebox_test(%4) : (!fir.box<!fir.array<?x?xf32>>) -> ()
+  return
+}