Normalize lowering of MemRef types

The RFC for unifying Linalg and Affine compilation passes into an end-to-end flow with a predictable ABI and linkage to external function calls raised the question of why we have variable sized descriptors for memrefs depending on whether they have static or dynamic dimensions  (https://groups.google.com/a/tensorflow.org/forum/#!topic/mlir/MaL8m2nXuio).

This CL standardizes the ABI on the rank of the memrefs.
The LLVM struct for a memref becomes equivalent to:
```
template <typename Elem, size_t Rank>
struct {
  Elem *ptr;
  int64_t sizes[Rank];
};
```

PiperOrigin-RevId: 270947276
diff --git a/examples/Linalg/Linalg1/lib/ConvertToLLVMDialect.cpp b/examples/Linalg/Linalg1/lib/ConvertToLLVMDialect.cpp
index 48beba7..276043c 100644
--- a/examples/Linalg/Linalg1/lib/ConvertToLLVMDialect.cpp
+++ b/examples/Linalg/Linalg1/lib/ConvertToLLVMDialect.cpp
@@ -181,8 +181,8 @@
     // Helper function to obtain the size of the given `memref` along the
     // dimension `dim`.  For static dimensions, emits a constant; for dynamic
     // dimensions, extracts the size from the memref descriptor.
-    auto memrefSize = [int64Ty, pos, i64cst](MemRefType type, Value *memref,
-                                             int dim) -> Value * {
+    auto memrefSize = [&rewriter, int64Ty, i64cst](
+                          MemRefType type, Value *memref, int dim) -> Value * {
       assert(dim < type.getRank());
       if (type.getShape()[dim] != -1) {
         return i64cst(type.getShape()[dim]);
@@ -191,14 +191,12 @@
       for (int i = 0; i < dim; ++i)
         if (type.getShape()[i] == -1)
           ++dynamicDimPos;
-      return intrinsics::extractvalue(int64Ty, memref, pos(1 + dynamicDimPos));
+      return intrinsics::extractvalue(
+          int64Ty, memref, rewriter.getI64ArrayAttr({1, dynamicDimPos}));
     };
 
     // Helper function to obtain the data pointer of the given `memref`.
     auto memrefPtr = [pos](MemRefType type, Value *memref) -> Value * {
-      if (type.hasStaticShape())
-        return memref;
-
       auto elementTy = linalg::convertLinalgType(type.getElementType())
                            .cast<LLVM::LLVMType>()
                            .getPointerTo();
diff --git a/examples/toy/Ch5/mlir/LateLowering.cpp b/examples/toy/Ch5/mlir/LateLowering.cpp
index 95585f9..28dc717 100644
--- a/examples/toy/Ch5/mlir/LateLowering.cpp
+++ b/examples/toy/Ch5/mlir/LateLowering.cpp
@@ -149,6 +149,7 @@
 
     // Create our loop nest now
     using namespace edsc;
+    using extractvalue = intrinsics::ValueBuilder<LLVM::ExtractValueOp>;
     using llvmCall = intrinsics::ValueBuilder<LLVM::CallOp>;
     ScopedContext scope(rewriter, loc);
     ValueHandle zero = intrinsics::constant_index(0);
@@ -157,26 +158,36 @@
     IndexedValue iOp(operand);
     IndexHandle i, j, M(vOp.ub(0));
 
+    auto *dialect = op->getContext()->getRegisteredDialect<LLVM::LLVMDialect>();
+    auto i8PtrTy = LLVM::LLVMType::getInt8Ty(dialect).getPointerTo();
+
     ValueHandle fmtEol(getConstantCharBuffer(rewriter, loc, "\n"));
     if (vOp.rank() == 1) {
       // clang-format off
       LoopBuilder(&i, zero, M, 1)([&]{
         llvmCall(retTy,
                  rewriter.getSymbolRefAttr(printfFunc),
-                 {fmtCst, iOp(i)});
+                 {extractvalue(i8PtrTy, fmtCst, rewriter.getIndexArrayAttr(0)),
+                  iOp(i)});
       });
-      llvmCall(retTy, rewriter.getSymbolRefAttr(printfFunc), {fmtEol});
+      llvmCall(retTy, rewriter.getSymbolRefAttr(printfFunc),
+               {extractvalue(i8PtrTy, fmtEol, rewriter.getIndexArrayAttr(0))});
       // clang-format on
     } else {
       IndexHandle N(vOp.ub(1));
       // clang-format off
       LoopBuilder(&i, zero, M, 1)([&]{
         LoopBuilder(&j, zero, N, 1)([&]{
-          llvmCall(retTy,
-                   rewriter.getSymbolRefAttr(printfFunc),
-                   {fmtCst, iOp(i, j)});
+          llvmCall(
+            retTy,
+            rewriter.getSymbolRefAttr(printfFunc),
+            {extractvalue(i8PtrTy, fmtCst, rewriter.getIndexArrayAttr(0)),
+             iOp(i, j)});
         });
-        llvmCall(retTy, rewriter.getSymbolRefAttr(printfFunc), {fmtEol});
+        llvmCall(
+          retTy,
+          rewriter.getSymbolRefAttr(printfFunc),
+          {extractvalue(i8PtrTy, fmtEol, rewriter.getIndexArrayAttr(0))});
       });
       // clang-format on
     }
diff --git a/g3doc/ConversionToLLVMDialect.md b/g3doc/ConversionToLLVMDialect.md
index 1e2ec95..6132615 100644
--- a/g3doc/ConversionToLLVMDialect.md
+++ b/g3doc/ConversionToLLVMDialect.md
@@ -52,36 +52,27 @@
 
 Memref types in MLIR have both static and dynamic information associated with
 them. The dynamic information comprises the buffer pointer as well as sizes of
-any dynamically sized dimensions. Memref types are converted into either LLVM IR
-pointer types if they are fully statically shaped; or to LLVM IR structure types
-if they contain dynamic sizes. In the latter case, the first element of the
-structure is a pointer to the converted (using these rules) memref element type,
-followed by as many elements as the memref has dynamic sizes. The type of each
-of these size arguments will be the LLVM type that results from converting the
-MLIR `index` type. Zero-dimensional memrefs are treated as pointers to the
-elemental type.
+any dynamically sized dimensions. Memref types are normalized and converted to a
+descriptor that is only dependent on the rank of the memref. The descriptor
+contains the pointer to the data buffer followed by an array containing as many
+64-bit integers as the rank of the memref. The array represents the size, in
+number of elements, of the memref along the given dimension. For constant memref
+dimensions, the corresponding size entry is a constant whose runtime value
+matches the static value. This normalization serves as an ABI for the memref
+type to interoperate with externally linked functions. In the particular case of
+rank `0` memrefs, the size array is omitted, resulting in a wrapped pointer.
 
 Examples:
 
 ```mlir {.mlir}
-// All of the following are converted to just a pointer type because
-// of fully static sizes.
-memref<f32>
-memref<1 x f32>
-memref<10x42x42x43x123 x f32>
-// resulting type
-!llvm.type<"float*">
-
-// All of the following are converted to a three-element structure
-memref<?x? x f32>
-memref<42x?x10x35x1x? x f32>
-// resulting type assuming 64-bit pointers
-!llvm.type<"{float*, i64, i64}">
+memref<f32> -> !llvm.type<"{ float* }">
+memref<1 x f32> -> !llvm.type<"{ float*, [1 x i64] }">
+memref<? x f32> -> !llvm.type<"{ float*, [1 x i64] }">
+memref<10x42x42x43x123 x f32> -> !llvm.type<"{ float*, [5 x i64] }">
+memref<10x?x42x?x123 x f32> -> !llvm.type<"{ float*, [5 x i64] }">
 
 // Memref types can have vectors as element types
-memref<1x? x vector<4xf32>>
-// which get converted as well
-!llvm.type<"{<4 x float>*, i64}">
+memref<1x? x vector<4xf32>> -> !llvm.type<"{ <4 x float>*, [1 x i64] }">
 ```
 
 ### Function Types
diff --git a/lib/Conversion/StandardToLLVM/ConvertStandardToLLVM.cpp b/lib/Conversion/StandardToLLVM/ConvertStandardToLLVM.cpp
index a67ff9d..968407a 100644
--- a/lib/Conversion/StandardToLLVM/ConvertStandardToLLVM.cpp
+++ b/lib/Conversion/StandardToLLVM/ConvertStandardToLLVM.cpp
@@ -122,27 +122,38 @@
       .getPointerTo();
 }
 
-// Convert a MemRef to an LLVM type. If the memref is statically-shaped, then
-// we return a pointer to the converted element type. Otherwise we return an
-// LLVM structure type, where the first element of the structure type is a
-// pointer to the elemental type of the MemRef and the following N elements are
-// values of the Index type, one for each of N dynamic dimensions of the MemRef.
+// Convert a MemRef to an LLVM type. The result is a MemRef descriptor which
+// contains:
+//   1. the pointer to the data buffer, followed by
+//   2. an array containing as many 64-bit integers as the rank of the MemRef:
+//   the array represents the size, in number of elements, of the memref along
+//   the given dimension. For constant MemRef dimensions, the corresponding size
+//   entry is a constant whose runtime value must match the static value.
+//   TODO(ntv, zinenko): add assertions for the static cases.
+//
+// template <typename Elem, size_t Rank>
+// struct {
+//   Elem *ptr;
+//   int64_t sizes[Rank]; // omitted when rank == 0
+// };
+static unsigned kPtrPosInMemRefDescriptor = 0;
+static unsigned kSizePosInMemRefDescriptor = 1;
 Type LLVMTypeConverter::convertMemRefType(MemRefType type) {
+  assert((type.getAffineMaps().empty() ||
+          (type.getAffineMaps().size() == 1 &&
+           type.getAffineMaps().back().isIdentity())) &&
+         "Non-identity layout maps must have been normalized away");
   LLVM::LLVMType elementType = unwrap(convertType(type.getElementType()));
   if (!elementType)
     return {};
-  auto ptrType = elementType.getPointerTo(type.getMemorySpace());
-
-  // Extra value for the memory space.
-  unsigned numDynamicSizes = type.getNumDynamicDims();
-  // If memref is statically-shaped we return the underlying pointer type.
-  if (numDynamicSizes == 0)
-    return ptrType;
-
-  SmallVector<LLVM::LLVMType, 8> types(numDynamicSizes + 1, getIndexType());
-  types.front() = ptrType;
-
-  return LLVM::LLVMType::getStructTy(llvmDialect, types);
+  auto ptrTy = elementType.getPointerTo(type.getMemorySpace());
+  auto indexTy = getIndexType();
+  auto rank = type.getRank();
+  if (rank > 0) {
+    auto arrayTy = LLVM::LLVMType::getArrayTy(indexTy, type.getRank());
+    return LLVM::LLVMType::getStructTy(ptrTy, arrayTy);
+  }
+  return LLVM::LLVMType::getStructTy(ptrTy);
 }
 
 // Convert an n-D vector type to an LLVM vector type via (n-1)-D array type when
@@ -600,10 +611,6 @@
     allocated = rewriter.create<LLVM::BitcastOp>(op->getLoc(), elementPtrType,
                                                  ArrayRef<Value *>(allocated));
 
-    // Deal with static memrefs
-    if (numOperands == 0)
-      return rewriter.replaceOp(op, allocated);
-
     // Create the MemRef descriptor.
     auto structType = lowering.convertType(type);
     Value *memRefDescriptor = rewriter.create<LLVM::UndefOp>(
@@ -611,14 +618,15 @@
 
     memRefDescriptor = rewriter.create<LLVM::InsertValueOp>(
         op->getLoc(), structType, memRefDescriptor, allocated,
-        rewriter.getIndexArrayAttr(0));
+        rewriter.getIndexArrayAttr(kPtrPosInMemRefDescriptor));
 
-    // Store dynamically allocated sizes in the descriptor.  Dynamic sizes are
-    // passed in as operands.
-    for (auto indexedSize : llvm::enumerate(operands)) {
+    // Store dynamically allocated sizes in the descriptor. Static and dynamic
+    // sizes are all passed in as operands.
+    for (auto indexedSize : llvm::enumerate(sizes)) {
+      int64_t index = indexedSize.index();
       memRefDescriptor = rewriter.create<LLVM::InsertValueOp>(
           op->getLoc(), structType, memRefDescriptor, indexedSize.value(),
-          rewriter.getIndexArrayAttr(1 + indexedSize.index()));
+          rewriter.getI64ArrayAttr({kSizePosInMemRefDescriptor, index}));
     }
 
     // Return the final value of the descriptor.
@@ -679,60 +687,12 @@
                ConversionPatternRewriter &rewriter) const override {
     auto memRefCastOp = cast<MemRefCastOp>(op);
     OperandAdaptor<MemRefCastOp> transformed(operands);
-    auto targetType = memRefCastOp.getType();
-    auto sourceType = memRefCastOp.getOperand()->getType().cast<MemRefType>();
-
-    // Copy the data buffer pointer.
-    auto elementTypePtr = getMemRefElementPtrType(targetType, lowering);
-    Value *buffer =
-        extractMemRefElementPtr(rewriter, op->getLoc(), transformed.source(),
-                                elementTypePtr, sourceType.hasStaticShape());
-    // Account for static memrefs as target types
-    if (targetType.hasStaticShape())
-      return rewriter.replaceOp(op, buffer);
-
-    // Create the new MemRef descriptor.
-    auto structType = lowering.convertType(targetType);
-    Value *newDescriptor = rewriter.create<LLVM::UndefOp>(
-        op->getLoc(), structType, ArrayRef<Value *>{});
-    // Otherwise target type is dynamic memref, so create a proper descriptor.
-    newDescriptor = rewriter.create<LLVM::InsertValueOp>(
-        op->getLoc(), structType, newDescriptor, buffer,
-        rewriter.getIndexArrayAttr(0));
-
-    // Fill in the dynamic sizes of the new descriptor.  If the size was
-    // dynamic, copy it from the old descriptor.  If the size was static, insert
-    // the constant.  Note that the positions of dynamic sizes in the
-    // descriptors start from 1 (the buffer pointer is at position zero).
-    int64_t sourceDynamicDimIdx = 1;
-    int64_t targetDynamicDimIdx = 1;
-    for (int i = 0, e = sourceType.getRank(); i < e; ++i) {
-      // Ignore new static sizes (they will be known from the type).  If the
-      // size was dynamic, update the index of dynamic types.
-      if (targetType.getShape()[i] != -1) {
-        if (sourceType.getShape()[i] == -1)
-          ++sourceDynamicDimIdx;
-        continue;
-      }
-
-      auto sourceSize = sourceType.getShape()[i];
-      Value *size =
-          sourceSize == -1
-              ? rewriter.create<LLVM::ExtractValueOp>(
-                    op->getLoc(), getIndexType(),
-                    transformed.source(), // NB: dynamic memref
-                    rewriter.getIndexArrayAttr(sourceDynamicDimIdx++))
-              : createIndexConstant(rewriter, op->getLoc(), sourceSize);
-      newDescriptor = rewriter.create<LLVM::InsertValueOp>(
-          op->getLoc(), structType, newDescriptor, size,
-          rewriter.getIndexArrayAttr(targetDynamicDimIdx++));
-    }
-    assert(sourceDynamicDimIdx - 1 == sourceType.getNumDynamicDims() &&
-           "source dynamic dimensions were not processed");
-    assert(targetDynamicDimIdx - 1 == targetType.getNumDynamicDims() &&
-           "target dynamic dimensions were not set up");
-
-    rewriter.replaceOp(op, newDescriptor);
+    // memref_cast is defined for source and destination memref types with the
+    // same element type, same mappings, same address space and same rank.
+    // Therefore a simple bitcast suffices. If not it is undefined behavior.
+    auto targetStructType = lowering.convertType(memRefCastOp.getType());
+    rewriter.replaceOpWithNewOp<LLVM::BitcastOp>(op, targetStructType,
+                                                 transformed.source());
   }
 };
 
@@ -754,25 +714,16 @@
     MemRefType type = dimOp.getOperand()->getType().cast<MemRefType>();
 
     auto shape = type.getShape();
-    uint64_t index = dimOp.getIndex();
+    int64_t index = dimOp.getIndex();
     // Extract dynamic size from the memref descriptor and define static size
     // as a constant.
-    if (shape[index] == -1) {
-      // Find the position of the dynamic dimension in the list of dynamic sizes
-      // by counting the number of preceding dynamic dimensions.  Start from 1
-      // because the buffer pointer is at position zero.
-      int64_t position = 1;
-      for (uint64_t i = 0; i < index; ++i) {
-        if (shape[i] == -1)
-          ++position;
-      }
+    if (ShapedType::isDynamic(shape[index]))
       rewriter.replaceOpWithNewOp<LLVM::ExtractValueOp>(
           op, getIndexType(), transformed.memrefOrTensor(),
-          rewriter.getIndexArrayAttr(position));
-    } else {
+          rewriter.getI64ArrayAttr({kSizePosInMemRefDescriptor, index}));
+    else
       rewriter.replaceOp(
           op, createIndexConstant(rewriter, op->getLoc(), shape[index]));
-    }
   }
 };
 
@@ -829,61 +780,41 @@
     // Dynamic sizes are extracted from the MemRef descriptor, where they start
     // from the position 1 (the buffer is at position 0).
     SmallVector<Value *, 4> sizes;
-    unsigned dynamicSizeIdx = 1;
-    for (int64_t s : shape) {
+    for (auto en : llvm::enumerate(shape)) {
+      int64_t s = en.value();
+      int64_t index = en.index();
       if (s == -1) {
         Value *size = rewriter.create<LLVM::ExtractValueOp>(
             loc, this->getIndexType(), memRefDescriptor,
-            rewriter.getIndexArrayAttr(dynamicSizeIdx++));
+            rewriter.getI64ArrayAttr({kSizePosInMemRefDescriptor, index}));
         sizes.push_back(size);
       } else {
         sizes.push_back(this->createIndexConstant(rewriter, loc, s));
+        // TODO(ntv, zinenko): assert dynamic descriptor size is constant.
       }
     }
 
     // The second and subsequent operands are access subscripts.  Obtain the
     // linearized address in the buffer.
-    Value *subscript = linearizeSubscripts(rewriter, loc, indices, sizes);
+    Value *subscript = indices.empty()
+                           ? nullptr
+                           : linearizeSubscripts(rewriter, loc, indices, sizes);
 
     Value *dataPtr = rewriter.create<LLVM::ExtractValueOp>(
-        loc, elementTypePtr, memRefDescriptor, rewriter.getIndexArrayAttr(0));
-    return rewriter.create<LLVM::GEPOp>(loc, elementTypePtr,
-                                        ArrayRef<Value *>{dataPtr, subscript},
+        loc, elementTypePtr, memRefDescriptor,
+        rewriter.getIndexArrayAttr(kPtrPosInMemRefDescriptor));
+    SmallVector<Value *, 2> gepSubValues(1, dataPtr);
+    if (subscript)
+      gepSubValues.push_back(subscript);
+    return rewriter.create<LLVM::GEPOp>(loc, elementTypePtr, gepSubValues,
                                         ArrayRef<NamedAttribute>{});
   }
-  // This is a getElementPtr variant, where the value is a direct raw pointer.
-  // If a shape is empty, we are dealing with a zero-dimensional memref. Return
-  // the pointer unmodified in this case.  Otherwise, linearize subscripts to
-  // obtain the offset with respect to the base pointer.  Use this offset to
-  // compute and return the element pointer.
-  Value *getRawElementPtr(Location loc, Type elementTypePtr,
-                          ArrayRef<int64_t> shape, Value *rawDataPtr,
-                          ArrayRef<Value *> indices,
-                          ConversionPatternRewriter &rewriter) const {
-    if (shape.empty())
-      return rawDataPtr;
-
-    SmallVector<Value *, 4> sizes;
-    for (int64_t s : shape) {
-      sizes.push_back(this->createIndexConstant(rewriter, loc, s));
-    }
-
-    Value *subscript = linearizeSubscripts(rewriter, loc, indices, sizes);
-    return rewriter.create<LLVM::GEPOp>(
-        loc, elementTypePtr, ArrayRef<Value *>{rawDataPtr, subscript},
-        ArrayRef<NamedAttribute>{});
-  }
-
   Value *getDataPtr(Location loc, MemRefType type, Value *dataPtr,
                     ArrayRef<Value *> indices,
                     ConversionPatternRewriter &rewriter,
                     llvm::Module &module) const {
     auto ptrType = getMemRefElementPtrType(type, this->lowering);
     auto shape = type.getShape();
-    if (type.hasStaticShape()) {
-      // NB: If memref was statically-shaped, dataPtr is pointer to raw data.
-      return getRawElementPtr(loc, ptrType, shape, dataPtr, indices, rewriter);
-    }
     return getElementPtr(loc, ptrType, shape, dataPtr, indices, rewriter);
   }
 };
diff --git a/test/Conversion/StandardToLLVM/convert-argattrs.mlir b/test/Conversion/StandardToLLVM/convert-argattrs.mlir
index 1617fd4..4c4b620 100644
--- a/test/Conversion/StandardToLLVM/convert-argattrs.mlir
+++ b/test/Conversion/StandardToLLVM/convert-argattrs.mlir
@@ -1,7 +1,7 @@
 // RUN: mlir-opt -lower-to-llvm %s | FileCheck %s
 
 
-// CHECK-LABEL: func @check_attributes(%arg0: !llvm<"float*"> {dialect.a = true, dialect.b = 4 : i64}) {
+// CHECK-LABEL: func @check_attributes(%arg0: !llvm<"{ float*, [2 x i64] }"> {dialect.a = true, dialect.b = 4 : i64}) {
 func @check_attributes(%static: memref<10x20xf32> {dialect.a = true, dialect.b = 4 : i64 }) {
   return
 }
diff --git a/test/Conversion/StandardToLLVM/convert-memref-ops.mlir b/test/Conversion/StandardToLLVM/convert-memref-ops.mlir
index 8cd1e98..496a1ec 100644
--- a/test/Conversion/StandardToLLVM/convert-memref-ops.mlir
+++ b/test/Conversion/StandardToLLVM/convert-memref-ops.mlir
@@ -1,18 +1,18 @@
 // RUN: mlir-opt -lower-to-llvm %s | FileCheck %s
 
 
-// CHECK-LABEL: func @check_arguments(%arg0: !llvm<"float*">, %arg1: !llvm<"{ float*, i64, i64 }">, %arg2: !llvm<"{ float*, i64 }">)
+// CHECK-LABEL: func @check_arguments(%arg0: !llvm<"{ float*, [2 x i64] }">, %arg1: !llvm<"{ float*, [2 x i64] }">, %arg2: !llvm<"{ float*, [2 x i64] }">)
 func @check_arguments(%static: memref<10x20xf32>, %dynamic : memref<?x?xf32>, %mixed : memref<10x?xf32>) {
   return
 }
 
-// CHECK-LABEL: func @check_static_return(%arg0: !llvm<"float*">) -> !llvm<"float*"> {
+// CHECK-LABEL: func @check_static_return(%arg0: !llvm<"{ float*, [2 x i64] }">) -> !llvm<"{ float*, [2 x i64] }"> {
 func @check_static_return(%static : memref<32x18xf32>) -> memref<32x18xf32> {
-// CHECK-NEXT:  llvm.return %arg0 : !llvm<"float*">
+// CHECK-NEXT:  llvm.return %arg0 : !llvm<"{ float*, [2 x i64] }">
   return %static : memref<32x18xf32>
 }
 
-// CHECK-LABEL: func @zero_d_alloc() -> !llvm<"float*"> {
+// CHECK-LABEL: func @zero_d_alloc() -> !llvm<"{ float* }"> {
 func @zero_d_alloc() -> memref<f32> {
 // CHECK-NEXT:  %0 = llvm.mlir.constant(1 : index) : !llvm.i64
 // CHECK-NEXT:  %1 = llvm.mlir.constant(4 : index) : !llvm.i64
@@ -23,15 +23,16 @@
   return %0 : memref<f32>
 }
 
-// CHECK-LABEL: func @zero_d_dealloc(%arg0: !llvm<"float*">) {
+// CHECK-LABEL: func @zero_d_dealloc(%arg0: !llvm<"{ float* }">) {
 func @zero_d_dealloc(%arg0: memref<f32>) {
-// CHECK-NEXT:  %0 = llvm.bitcast %arg0 : !llvm<"float*"> to !llvm<"i8*">
-// CHECK-NEXT:  llvm.call @free(%0) : (!llvm<"i8*">) -> ()
+// CHECK-NEXT:  %[[ptr:.*]] = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float* }">
+// CHECK-NEXT:  %[[bc:.*]] = llvm.bitcast %[[ptr]] : !llvm<"float*"> to !llvm<"i8*">
+// CHECK-NEXT:  llvm.call @free(%[[bc]]) : (!llvm<"i8*">) -> ()
   dealloc %arg0 : memref<f32>
   return
 }
 
-// CHECK-LABEL: func @mixed_alloc(%arg0: !llvm.i64, %arg1: !llvm.i64) -> !llvm<"{ float*, i64, i64 }"> {
+// CHECK-LABEL: func @mixed_alloc(%arg0: !llvm.i64, %arg1: !llvm.i64) -> !llvm<"{ float*, [3 x i64] }"> {
 func @mixed_alloc(%arg0: index, %arg1: index) -> memref<?x42x?xf32> {
 // CHECK-NEXT:  %0 = llvm.mlir.constant(42 : index) : !llvm.i64
 // CHECK-NEXT:  %1 = llvm.mul %arg0, %0 : !llvm.i64
@@ -40,17 +41,18 @@
 // CHECK-NEXT:  %4 = llvm.mul %2, %3 : !llvm.i64
 // CHECK-NEXT:  %5 = llvm.call @malloc(%4) : (!llvm.i64) -> !llvm<"i8*">
 // CHECK-NEXT:  %6 = llvm.bitcast %5 : !llvm<"i8*"> to !llvm<"float*">
-// CHECK-NEXT:  %7 = llvm.mlir.undef : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %8 = llvm.insertvalue %6, %7[0 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %9 = llvm.insertvalue %arg0, %8[1 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %10 = llvm.insertvalue %arg1, %9[2 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  %7 = llvm.mlir.undef : !llvm<"{ float*, [3 x i64] }">
+// CHECK-NEXT:  %8 = llvm.insertvalue %6, %7[0 : index] : !llvm<"{ float*, [3 x i64] }">
+// CHECK-NEXT:  %9 = llvm.insertvalue %arg0, %8[1, 0] : !llvm<"{ float*, [3 x i64] }">
+// CHECK-NEXT:  %10 = llvm.insertvalue %0, %9[1, 1] : !llvm<"{ float*, [3 x i64] }">
+// CHECK-NEXT:  %11 = llvm.insertvalue %arg1, %10[1, 2] : !llvm<"{ float*, [3 x i64] }">
   %0 = alloc(%arg0, %arg1) : memref<?x42x?xf32>
   return %0 : memref<?x42x?xf32>
 }
 
-// CHECK-LABEL: func @mixed_dealloc(%arg0: !llvm<"{ float*, i64, i64 }">) {
+// CHECK-LABEL: func @mixed_dealloc(%arg0: !llvm<"{ float*, [3 x i64] }">) {
 func @mixed_dealloc(%arg0: memref<?x42x?xf32>) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [3 x i64] }">
 // CHECK-NEXT:  %1 = llvm.bitcast %0 : !llvm<"float*"> to !llvm<"i8*">
 // CHECK-NEXT:  llvm.call @free(%1) : (!llvm<"i8*">) -> ()
   dealloc %arg0 : memref<?x42x?xf32>
@@ -58,31 +60,31 @@
   return
 }
 
-// CHECK-LABEL: func @dynamic_alloc(%arg0: !llvm.i64, %arg1: !llvm.i64) -> !llvm<"{ float*, i64, i64 }"> {
+// CHECK-LABEL: func @dynamic_alloc(%arg0: !llvm.i64, %arg1: !llvm.i64) -> !llvm<"{ float*, [2 x i64] }"> {
 func @dynamic_alloc(%arg0: index, %arg1: index) -> memref<?x?xf32> {
 // CHECK-NEXT:  %0 = llvm.mul %arg0, %arg1 : !llvm.i64
 // CHECK-NEXT:  %1 = llvm.mlir.constant(4 : index) : !llvm.i64
 // CHECK-NEXT:  %2 = llvm.mul %0, %1 : !llvm.i64
 // CHECK-NEXT:  %3 = llvm.call @malloc(%2) : (!llvm.i64) -> !llvm<"i8*">
 // CHECK-NEXT:  %4 = llvm.bitcast %3 : !llvm<"i8*"> to !llvm<"float*">
-// CHECK-NEXT:  %5 = llvm.mlir.undef : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %6 = llvm.insertvalue %4, %5[0 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %7 = llvm.insertvalue %arg0, %6[1 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %8 = llvm.insertvalue %arg1, %7[2 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  %5 = llvm.mlir.undef : !llvm<"{ float*, [2 x i64] }">
+// CHECK-NEXT:  %6 = llvm.insertvalue %4, %5[0 : index] : !llvm<"{ float*, [2 x i64] }">
+// CHECK-NEXT:  %7 = llvm.insertvalue %arg0, %6[1, 0] : !llvm<"{ float*, [2 x i64] }">
+// CHECK-NEXT:  %8 = llvm.insertvalue %arg1, %7[1, 1] : !llvm<"{ float*, [2 x i64] }">
   %0 = alloc(%arg0, %arg1) : memref<?x?xf32>
   return %0 : memref<?x?xf32>
 }
 
-// CHECK-LABEL: func @dynamic_dealloc(%arg0: !llvm<"{ float*, i64, i64 }">) {
+// CHECK-LABEL: func @dynamic_dealloc(%arg0: !llvm<"{ float*, [2 x i64] }">) {
 func @dynamic_dealloc(%arg0: memref<?x?xf32>) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %1 = llvm.bitcast %0 : !llvm<"float*"> to !llvm<"i8*">
 // CHECK-NEXT:  llvm.call @free(%1) : (!llvm<"i8*">) -> ()
   dealloc %arg0 : memref<?x?xf32>
   return
 }
 
-// CHECK-LABEL: func @static_alloc() -> !llvm<"float*"> {
+// CHECK-LABEL: func @static_alloc() -> !llvm<"{ float*, [2 x i64] }"> {
 func @static_alloc() -> memref<32x18xf32> {
 // CHECK-NEXT:  %0 = llvm.mlir.constant(32 : index) : !llvm.i64
 // CHECK-NEXT:  %1 = llvm.mlir.constant(18 : index) : !llvm.i64
@@ -95,17 +97,20 @@
  return %0 : memref<32x18xf32>
 }
 
-// CHECK-LABEL: func @static_dealloc(%arg0: !llvm<"float*">) {
+// CHECK-LABEL: func @static_dealloc(%arg0: !llvm<"{ float*, [2 x i64] }">) {
 func @static_dealloc(%static: memref<10x8xf32>) {
-// CHECK-NEXT:  %0 = llvm.bitcast %arg0 : !llvm<"float*"> to !llvm<"i8*">
-// CHECK-NEXT:  llvm.call @free(%0) : (!llvm<"i8*">) -> ()
+// CHECK-NEXT:  %[[ptr:.*]] = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [2 x i64] }">
+// CHECK-NEXT:  %[[bc:.*]] = llvm.bitcast %[[ptr]] : !llvm<"float*"> to !llvm<"i8*">
+// CHECK-NEXT:  llvm.call @free(%[[bc]]) : (!llvm<"i8*">) -> ()
   dealloc %static : memref<10x8xf32>
   return
 }
 
-// CHECK-LABEL: func @zero_d_load(%arg0: !llvm<"float*">) -> !llvm.float {
+// CHECK-LABEL: func @zero_d_load(%arg0: !llvm<"{ float* }">) -> !llvm.float {
 func @zero_d_load(%arg0: memref<f32>) -> f32 {
-// CHECK-NEXT:  %0 = llvm.load %arg0 : !llvm<"float*">
+// CHECK-NEXT:  %[[ptr:.*]] = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float* }">
+// CHECK-NEXT:  %[[addr:.*]] = llvm.getelementptr %[[ptr]][] : (!llvm<"float*">) -> !llvm<"float*">
+// CHECK-NEXT:  %2 = llvm.load %[[addr]] : !llvm<"float*">
   %0 = load %arg0[] : memref<f32>
   return %0 : f32
 }
@@ -116,8 +121,9 @@
 // CHECK-NEXT:  %1 = llvm.mlir.constant(42 : index) : !llvm.i64
 // CHECK-NEXT:  %2 = llvm.mul %arg1, %1 : !llvm.i64
 // CHECK-NEXT:  %3 = llvm.add %2, %arg2 : !llvm.i64
-// CHECK-NEXT:  %4 = llvm.getelementptr %arg0[%3] : (!llvm<"float*">, !llvm.i64) -> !llvm<"float*">
-// CHECK-NEXT:  %5 = llvm.load %4 : !llvm<"float*">
+// CHECK-NEXT:  %[[ptr:.*]] = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [2 x i64] }">
+// CHECK-NEXT:  %[[addr:.*]] = llvm.getelementptr %[[ptr]][%3] : (!llvm<"float*">, !llvm.i64) -> !llvm<"float*">
+// CHECK-NEXT:  llvm.load %[[addr]] : !llvm<"float*">
   %0 = load %static[%i, %j] : memref<10x42xf32>
   return
 }
@@ -125,10 +131,10 @@
 // CHECK-LABEL: func @mixed_load
 func @mixed_load(%mixed : memref<42x?xf32>, %i : index, %j : index) {
 // CHECK-NEXT:  %0 = llvm.mlir.constant(42 : index) : !llvm.i64
-// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[1 : index] : !llvm<"{ float*, i64 }">
+// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[1, 1] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %2 = llvm.mul %arg1, %1 : !llvm.i64
 // CHECK-NEXT:  %3 = llvm.add %2, %arg2 : !llvm.i64
-// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64 }">
+// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %5 = llvm.getelementptr %4[%3] : (!llvm<"float*">, !llvm.i64) -> !llvm<"float*">
 // CHECK-NEXT:  %6 = llvm.load %5 : !llvm<"float*">
   %0 = load %mixed[%i, %j] : memref<42x?xf32>
@@ -137,20 +143,22 @@
 
 // CHECK-LABEL: func @dynamic_load
 func @dynamic_load(%dynamic : memref<?x?xf32>, %i : index, %j : index) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[1 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[2 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[1, 0] : !llvm<"{ float*, [2 x i64] }">
+// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[1, 1] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %2 = llvm.mul %arg1, %1 : !llvm.i64
 // CHECK-NEXT:  %3 = llvm.add %2, %arg2 : !llvm.i64
-// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %5 = llvm.getelementptr %4[%3] : (!llvm<"float*">, !llvm.i64) -> !llvm<"float*">
 // CHECK-NEXT:  %6 = llvm.load %5 : !llvm<"float*">
   %0 = load %dynamic[%i, %j] : memref<?x?xf32>
   return
 }
 
-// CHECK-LABEL: func @zero_d_store(%arg0: !llvm<"float*">, %arg1: !llvm.float) {
+// CHECK-LABEL: func @zero_d_store(%arg0: !llvm<"{ float* }">, %arg1: !llvm.float) {
 func @zero_d_store(%arg0: memref<f32>, %arg1: f32) {
-// CHECK-NEXT:  llvm.store %arg1, %arg0 : !llvm<"float*">
+// CHECK-NEXT:  %[[ptr:.*]] = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float* }">
+// CHECK-NEXT:  %[[addr:.*]] = llvm.getelementptr %[[ptr]][] : (!llvm<"float*">) -> !llvm<"float*">
+// CHECK-NEXT:  llvm.store %arg1, %[[addr]] : !llvm<"float*">
   store %arg1, %arg0[] : memref<f32>
   return
 }
@@ -161,19 +169,20 @@
 // CHECK-NEXT:  %1 = llvm.mlir.constant(42 : index) : !llvm.i64
 // CHECK-NEXT:  %2 = llvm.mul %arg1, %1 : !llvm.i64
 // CHECK-NEXT:  %3 = llvm.add %2, %arg2 : !llvm.i64
-// CHECK-NEXT:  %4 = llvm.getelementptr %arg0[%3] : (!llvm<"float*">, !llvm.i64) -> !llvm<"float*">
-// CHECK-NEXT:  llvm.store %arg3, %4 : !llvm<"float*">
+// CHECK-NEXT:  %[[ptr:.*]] = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [2 x i64] }">
+// CHECK-NEXT:  %[[addr:.*]] = llvm.getelementptr %[[ptr]][%3] : (!llvm<"float*">, !llvm.i64) -> !llvm<"float*">
+// CHECK-NEXT:  llvm.store %arg3, %[[addr]] : !llvm<"float*">
   store %val, %static[%i, %j] : memref<10x42xf32>
   return
 }
 
 // CHECK-LABEL: func @dynamic_store
 func @dynamic_store(%dynamic : memref<?x?xf32>, %i : index, %j : index, %val : f32) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[1 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[2 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[1, 0] : !llvm<"{ float*, [2 x i64] }">
+// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[1, 1] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %2 = llvm.mul %arg1, %1 : !llvm.i64
 // CHECK-NEXT:  %3 = llvm.add %2, %arg2 : !llvm.i64
-// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %5 = llvm.getelementptr %4[%3] : (!llvm<"float*">, !llvm.i64) -> !llvm<"float*">
 // CHECK-NEXT:  llvm.store %arg3, %5 : !llvm<"float*">
   store %val, %dynamic[%i, %j] : memref<?x?xf32>
@@ -183,10 +192,10 @@
 // CHECK-LABEL: func @mixed_store
 func @mixed_store(%mixed : memref<42x?xf32>, %i : index, %j : index, %val : f32) {
 // CHECK-NEXT:  %0 = llvm.mlir.constant(42 : index) : !llvm.i64
-// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[1 : index] : !llvm<"{ float*, i64 }">
+// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[1, 1] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %2 = llvm.mul %arg1, %1 : !llvm.i64
 // CHECK-NEXT:  %3 = llvm.add %2, %arg2 : !llvm.i64
-// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64 }">
+// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, [2 x i64] }">
 // CHECK-NEXT:  %5 = llvm.getelementptr %4[%3] : (!llvm<"float*">, !llvm.i64) -> !llvm<"float*">
 // CHECK-NEXT:  llvm.store %arg3, %5 : !llvm<"float*">
   store %val, %mixed[%i, %j] : memref<42x?xf32>
@@ -195,91 +204,69 @@
 
 // CHECK-LABEL: func @memref_cast_static_to_dynamic
 func @memref_cast_static_to_dynamic(%static : memref<10x42xf32>) {
-// CHECK-NEXT:  %0 = llvm.mlir.undef : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %1 = llvm.insertvalue %arg0, %0[0 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %2 = llvm.mlir.constant(10 : index) : !llvm.i64
-// CHECK-NEXT:  %3 = llvm.insertvalue %2, %1[1 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %4 = llvm.mlir.constant(42 : index) : !llvm.i64
-// CHECK-NEXT:  %5 = llvm.insertvalue %4, %3[2 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  llvm.bitcast %arg0 : !llvm<"{ float*, [2 x i64] }"> to !llvm<"{ float*, [2 x i64] }">
   %0 = memref_cast %static : memref<10x42xf32> to memref<?x?xf32>
   return
 }
 
 // CHECK-LABEL: func @memref_cast_static_to_mixed
 func @memref_cast_static_to_mixed(%static : memref<10x42xf32>) {
-// CHECK-NEXT:  %0 = llvm.mlir.undef : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %1 = llvm.insertvalue %arg0, %0[0 : index] : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %2 = llvm.mlir.constant(10 : index) : !llvm.i64
-// CHECK-NEXT:  %3 = llvm.insertvalue %2, %1[1 : index] : !llvm<"{ float*, i64 }">
+// CHECK-NEXT:  llvm.bitcast %arg0 : !llvm<"{ float*, [2 x i64] }"> to !llvm<"{ float*, [2 x i64] }">
   %0 = memref_cast %static : memref<10x42xf32> to memref<?x42xf32>
   return
 }
 
 // CHECK-LABEL: func @memref_cast_dynamic_to_static
 func @memref_cast_dynamic_to_static(%dynamic : memref<?x?xf32>) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  llvm.bitcast %arg0 : !llvm<"{ float*, [2 x i64] }"> to !llvm<"{ float*, [2 x i64] }">
   %0 = memref_cast %dynamic : memref<?x?xf32> to memref<10x12xf32>
   return
 }
 
 // CHECK-LABEL: func @memref_cast_dynamic_to_mixed
 func @memref_cast_dynamic_to_mixed(%dynamic : memref<?x?xf32>) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %1 = llvm.mlir.undef : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %2 = llvm.insertvalue %0, %1[0 : index] : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %3 = llvm.extractvalue %arg0[1 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %4 = llvm.insertvalue %3, %2[1 : index] : !llvm<"{ float*, i64 }">
+// CHECK-NEXT:  llvm.bitcast %arg0 : !llvm<"{ float*, [2 x i64] }"> to !llvm<"{ float*, [2 x i64] }">
   %0 = memref_cast %dynamic : memref<?x?xf32> to memref<?x12xf32>
   return
 }
 
 // CHECK-LABEL: func @memref_cast_mixed_to_dynamic
 func @memref_cast_mixed_to_dynamic(%mixed : memref<42x?xf32>) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %1 = llvm.mlir.undef : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %2 = llvm.insertvalue %0, %1[0 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %3 = llvm.mlir.constant(42 : index) : !llvm.i64
-// CHECK-NEXT:  %4 = llvm.insertvalue %3, %2[1 : index] : !llvm<"{ float*, i64, i64 }">
-// CHECK-NEXT:  %5 = llvm.extractvalue %arg0[1 : index] : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %6 = llvm.insertvalue %5, %4[2 : index] : !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  llvm.bitcast %arg0 : !llvm<"{ float*, [2 x i64] }"> to !llvm<"{ float*, [2 x i64] }">
   %0 = memref_cast %mixed : memref<42x?xf32> to memref<?x?xf32>
   return
 }
 
 // CHECK-LABEL: func @memref_cast_mixed_to_static
 func @memref_cast_mixed_to_static(%mixed : memref<42x?xf32>) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64 }">
+// CHECK-NEXT:  llvm.bitcast %arg0 : !llvm<"{ float*, [2 x i64] }"> to !llvm<"{ float*, [2 x i64] }">
   %0 = memref_cast %mixed : memref<42x?xf32> to memref<42x1xf32>
   return
 }
 
 // CHECK-LABEL: func @memref_cast_mixed_to_mixed
 func @memref_cast_mixed_to_mixed(%mixed : memref<42x?xf32>) {
-// CHECK-NEXT:  %0 = llvm.extractvalue %arg0[0 : index] : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %1 = llvm.mlir.undef : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %2 = llvm.insertvalue %0, %1[0 : index] : !llvm<"{ float*, i64 }">
-// CHECK-NEXT:  %3 = llvm.mlir.constant(42 : index) : !llvm.i64
-// CHECK-NEXT:  %4 = llvm.insertvalue %3, %2[1 : index] : !llvm<"{ float*, i64 }">
+// CHECK-NEXT:  llvm.bitcast %arg0 : !llvm<"{ float*, [2 x i64] }"> to !llvm<"{ float*, [2 x i64] }">
   %0 = memref_cast %mixed : memref<42x?xf32> to memref<?x1xf32>
   return
 }
 
-// CHECK-LABEL: func @mixed_memref_dim(%arg0: !llvm<"{ float*, i64, i64, i64 }">)
+// CHECK-LABEL: func @mixed_memref_dim(%arg0: !llvm<"{ float*, [5 x i64] }">)
 func @mixed_memref_dim(%mixed : memref<42x?x?x13x?xf32>) {
 // CHECK-NEXT:  %0 = llvm.mlir.constant(42 : index) : !llvm.i64
   %0 = dim %mixed, 0 : memref<42x?x?x13x?xf32>
-// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[1 : index] : !llvm<"{ float*, i64, i64, i64 }">
+// CHECK-NEXT:  %1 = llvm.extractvalue %arg0[1, 1] : !llvm<"{ float*, [5 x i64] }">
   %1 = dim %mixed, 1 : memref<42x?x?x13x?xf32>
-// CHECK-NEXT:  %2 = llvm.extractvalue %arg0[2 : index] : !llvm<"{ float*, i64, i64, i64 }">
+// CHECK-NEXT:  %2 = llvm.extractvalue %arg0[1, 2] : !llvm<"{ float*, [5 x i64] }">
   %2 = dim %mixed, 2 : memref<42x?x?x13x?xf32>
 // CHECK-NEXT:  %3 = llvm.mlir.constant(13 : index) : !llvm.i64
   %3 = dim %mixed, 3 : memref<42x?x?x13x?xf32>
-// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[3 : index] : !llvm<"{ float*, i64, i64, i64 }">
+// CHECK-NEXT:  %4 = llvm.extractvalue %arg0[1, 4] : !llvm<"{ float*, [5 x i64] }">
   %4 = dim %mixed, 4 : memref<42x?x?x13x?xf32>
   return
 }
 
-// CHECK-LABEL: func @static_memref_dim(%arg0: !llvm<"float*">)
+// CHECK-LABEL: func @static_memref_dim(%arg0: !llvm<"{ float*, [5 x i64] }">)
 func @static_memref_dim(%static : memref<42x32x15x13x27xf32>) {
 // CHECK-NEXT:  %0 = llvm.mlir.constant(42 : index) : !llvm.i64
   %0 = dim %static, 0 : memref<42x32x15x13x27xf32>
diff --git a/test/Conversion/StandardToLLVM/convert-to-llvmir.mlir b/test/Conversion/StandardToLLVM/convert-to-llvmir.mlir
index b81f7fb..97054f6 100644
--- a/test/Conversion/StandardToLLVM/convert-to-llvmir.mlir
+++ b/test/Conversion/StandardToLLVM/convert-to-llvmir.mlir
@@ -317,23 +317,23 @@
 func @get_i64() -> (i64)
 // CHECK-LABEL: func @get_f32() -> !llvm.float
 func @get_f32() -> (f32)
-// CHECK-LABEL: func @get_memref() -> !llvm<"{ float*, i64, i64 }">
+// CHECK-LABEL: func @get_memref() -> !llvm<"{ float*, [4 x i64] }">
 func @get_memref() -> (memref<42x?x10x?xf32>)
 
-// CHECK-LABEL: func @multireturn() -> !llvm<"{ i64, float, { float*, i64, i64 } }"> {
+// CHECK-LABEL: func @multireturn() -> !llvm<"{ i64, float, { float*, [4 x i64] } }"> {
 func @multireturn() -> (i64, f32, memref<42x?x10x?xf32>) {
 ^bb0:
 // CHECK-NEXT:  {{.*}} = llvm.call @get_i64() : () -> !llvm.i64
 // CHECK-NEXT:  {{.*}} = llvm.call @get_f32() : () -> !llvm.float
-// CHECK-NEXT:  {{.*}} = llvm.call @get_memref() : () -> !llvm<"{ float*, i64, i64 }">
+// CHECK-NEXT:  {{.*}} = llvm.call @get_memref() : () -> !llvm<"{ float*, [4 x i64] }">
   %0 = call @get_i64() : () -> (i64)
   %1 = call @get_f32() : () -> (f32)
   %2 = call @get_memref() : () -> (memref<42x?x10x?xf32>)
-// CHECK-NEXT:  {{.*}} = llvm.mlir.undef : !llvm<"{ i64, float, { float*, i64, i64 } }">
-// CHECK-NEXT:  {{.*}} = llvm.insertvalue {{.*}}, {{.*}}[0 : index] : !llvm<"{ i64, float, { float*, i64, i64 } }">
-// CHECK-NEXT:  {{.*}} = llvm.insertvalue {{.*}}, {{.*}}[1 : index] : !llvm<"{ i64, float, { float*, i64, i64 } }">
-// CHECK-NEXT:  {{.*}} = llvm.insertvalue {{.*}}, {{.*}}[2 : index] : !llvm<"{ i64, float, { float*, i64, i64 } }">
-// CHECK-NEXT:  llvm.return {{.*}} : !llvm<"{ i64, float, { float*, i64, i64 } }">
+// CHECK-NEXT:  {{.*}} = llvm.mlir.undef : !llvm<"{ i64, float, { float*, [4 x i64] } }">
+// CHECK-NEXT:  {{.*}} = llvm.insertvalue {{.*}}, {{.*}}[0 : index] : !llvm<"{ i64, float, { float*, [4 x i64] } }">
+// CHECK-NEXT:  {{.*}} = llvm.insertvalue {{.*}}, {{.*}}[1 : index] : !llvm<"{ i64, float, { float*, [4 x i64] } }">
+// CHECK-NEXT:  {{.*}} = llvm.insertvalue {{.*}}, {{.*}}[2 : index] : !llvm<"{ i64, float, { float*, [4 x i64] } }">
+// CHECK-NEXT:  llvm.return {{.*}} : !llvm<"{ i64, float, { float*, [4 x i64] } }">
   return %0, %1, %2 : i64, f32, memref<42x?x10x?xf32>
 }
 
@@ -341,10 +341,10 @@
 // CHECK-LABEL: func @multireturn_caller() {
 func @multireturn_caller() {
 ^bb0:
-// CHECK-NEXT:  {{.*}} = llvm.call @multireturn() : () -> !llvm<"{ i64, float, { float*, i64, i64 } }">
-// CHECK-NEXT:  {{.*}} = llvm.extractvalue {{.*}}[0 : index] : !llvm<"{ i64, float, { float*, i64, i64 } }">
-// CHECK-NEXT:  {{.*}} = llvm.extractvalue {{.*}}[1 : index] : !llvm<"{ i64, float, { float*, i64, i64 } }">
-// CHECK-NEXT:  {{.*}} = llvm.extractvalue {{.*}}[2 : index] : !llvm<"{ i64, float, { float*, i64, i64 } }">
+// CHECK-NEXT:  {{.*}} = llvm.call @multireturn() : () -> !llvm<"{ i64, float, { float*, [4 x i64] } }">
+// CHECK-NEXT:  {{.*}} = llvm.extractvalue {{.*}}[0 : index] : !llvm<"{ i64, float, { float*, [4 x i64] } }">
+// CHECK-NEXT:  {{.*}} = llvm.extractvalue {{.*}}[1 : index] : !llvm<"{ i64, float, { float*, [4 x i64] } }">
+// CHECK-NEXT:  {{.*}} = llvm.extractvalue {{.*}}[2 : index] : !llvm<"{ i64, float, { float*, [4 x i64] } }">
   %0:3 = call @multireturn() : () -> (i64, f32, memref<42x?x10x?xf32>)
   %1 = constant 42 : i64
 // CHECK:       {{.*}} = llvm.add {{.*}}, {{.*}} : !llvm.i64
diff --git a/test/Conversion/StandardToLLVM/standard-to-llvm.mlir b/test/Conversion/StandardToLLVM/standard-to-llvm.mlir
index 9afb087..3f62fd4 100644
--- a/test/Conversion/StandardToLLVM/standard-to-llvm.mlir
+++ b/test/Conversion/StandardToLLVM/standard-to-llvm.mlir
@@ -1,6 +1,7 @@
 // RUN: mlir-opt %s -lower-to-llvm | FileCheck %s
 
-// CHECK-LABEL: func @address_space(%{{.*}}: !llvm<"float addrspace(7)*">)
+// CHECK-LABEL: func @address_space(
+//       CHECK:   %{{.*}}: !llvm<"{ float addrspace(7)*, [1 x i64] }">)
 func @address_space(%arg0 : memref<32xf32, (d0) -> (d0), 7>) {
   %0 = alloc() : memref<32xf32, (d0) -> (d0), 5>
   %1 = constant 7 : index
diff --git a/test/Examples/Linalg/Linalg1.mlir b/test/Examples/Linalg/Linalg1.mlir
index a5a3bac..03e16db 100644
--- a/test/Examples/Linalg/Linalg1.mlir
+++ b/test/Examples/Linalg/Linalg1.mlir
@@ -64,9 +64,9 @@
 }
 // LLVM-LABEL: @viewRangeConversion
 // LLVM-NEXT: %0 = llvm.mlir.undef : !llvm<"{ float*, i64, [2 x i64], [2 x i64] }">
-// LLVM-NEXT: %1 = llvm.extractvalue %arg0[0] : !llvm<"{ float*, i64, i64 }">
+// LLVM-NEXT: %1 = llvm.extractvalue %arg0[0] : !llvm<"{ float*, [2 x i64] }">
 // LLVM-NEXT: %2 = llvm.insertvalue %1, %0[0] : !llvm<"{ float*, i64, [2 x i64], [2 x i64] }">
-// LLVM-NEXT: %3 = llvm.extractvalue %arg0[2] : !llvm<"{ float*, i64, i64 }">
+// LLVM-NEXT: %3 = llvm.extractvalue %arg0[1, 1] : !llvm<"{ float*, [2 x i64] }">
 // LLVM-NEXT: %4 = llvm.mlir.constant(1 : index) : !llvm.i64
 // LLVM-NEXT: %5 = llvm.mul %4, %3 : !llvm.i64
 // LLVM-NEXT: %6 = llvm.mlir.constant(0 : index) : !llvm.i64
@@ -98,9 +98,9 @@
 }
 // LLVM-LABEL: @viewNonRangeConversion
 // LLVM-NEXT: %0 = llvm.mlir.undef : !llvm<"{ float*, i64, [1 x i64], [1 x i64] }">
-// LLVM-NEXT: %1 = llvm.extractvalue %arg0[0] : !llvm<"{ float*, i64, i64 }">
+// LLVM-NEXT: %1 = llvm.extractvalue %arg0[0] : !llvm<"{ float*, [2 x i64] }">
 // LLVM-NEXT: %2 = llvm.insertvalue %1, %0[0] : !llvm<"{ float*, i64, [1 x i64], [1 x i64] }">
-// LLVM-NEXT: %3 = llvm.extractvalue %arg0[2] : !llvm<"{ float*, i64, i64 }">
+// LLVM-NEXT: %3 = llvm.extractvalue %arg0[1, 1] : !llvm<"{ float*, [2 x i64] }">
 // LLVM-NEXT: %4 = llvm.mlir.constant(1 : index) : !llvm.i64
 // LLVM-NEXT: %5 = llvm.mul %4, %3 : !llvm.i64
 // LLVM-NEXT: %6 = llvm.mlir.constant(0 : index) : !llvm.i64