[flang] Implement more transformational intrinsic functions in runtime
authorpeter klausler <pklausler@nvidia.com>
Thu, 20 May 2021 17:37:03 +0000 (10:37 -0700)
committerpeter klausler <pklausler@nvidia.com>
Thu, 20 May 2021 20:22:01 +0000 (13:22 -0700)
Define APIs, naively implement, and add basic sanity unit tests for
the transformational intrinsic functions CSHIFT, EOSHIFT, PACK,
SPREAD, TRANSPOSE, and UNPACK.  These are the remaining transformational
intrinsic functions that rearrange data without regard to type
(except for default boundary values in EOSHIFT); RESHAPE was already
in place as a stress test for the runtime's descriptor handling
facilities.

Code is in place to create copies of allocatable/automatic
components when transforming arrays of derived type, but it won't
do anything until we have derived type information being passed to the
runtime from the frontend.

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

17 files changed:
flang/module/__fortran_type_info.f90
flang/runtime/CMakeLists.txt
flang/runtime/allocatable.cpp
flang/runtime/copy.cpp [new file with mode: 0644]
flang/runtime/copy.h [new file with mode: 0644]
flang/runtime/descriptor.h
flang/runtime/tools.cpp
flang/runtime/tools.h
flang/runtime/transformational.cpp
flang/runtime/transformational.h
flang/runtime/type-info.h
flang/unittests/Evaluate/reshape.cpp
flang/unittests/RuntimeGTest/CMakeLists.txt
flang/unittests/RuntimeGTest/Matmul.cpp
flang/unittests/RuntimeGTest/Namelist.cpp
flang/unittests/RuntimeGTest/Reduction.cpp
flang/unittests/RuntimeGTest/Transformational.cpp [new file with mode: 0644]

index 6fce352..c2a9ed1 100644 (file)
@@ -30,7 +30,7 @@ module __Fortran_type_info
     ! applied, appear in the initial entries in the same order as they
     ! appear in the parent type's bindings, if any.  They are followed
     ! by new local bindings in alphabetic order of theing binding names.
-    type(Binding), pointer :: binding(:)
+    type(Binding), pointer, contiguous :: binding(:)
     character(len=:), pointer :: name
     integer(kind=int64) :: sizeInBytes
     type(DerivedType), pointer :: parent
@@ -38,14 +38,14 @@ module __Fortran_type_info
     ! component to point to the pristine original definition.
     type(DerivedType), pointer :: uninstantiated
     integer(kind=int64) :: typeHash
-    integer(kind=int64), pointer :: kindParameter(:) ! values of instance
-    integer(1), pointer :: lenParameterKind(:) ! INTEGER kinds of LEN types
+    integer(kind=int64), pointer, contiguous :: kindParameter(:) ! values of instance
+    integer(1), pointer, contiguous :: lenParameterKind(:) ! INTEGER kinds of LEN types
     ! Data components appear in alphabetic order.
     ! The parent component, if any, appears explicitly.
-    type(Component), pointer :: component(:) ! data components
-    type(ProcPtrComponent), pointer :: procptr(:) ! procedure pointers
+    type(Component), pointer, contiguous :: component(:) ! data components
+    type(ProcPtrComponent), pointer, contiguous :: procptr(:) ! procedure pointers
     ! Special bindings of the ancestral types are not duplicated here.
-    type(SpecialBinding), pointer :: special(:)
+    type(SpecialBinding), pointer, contiguous :: special(:)
   end type
 
   type :: Binding
@@ -86,8 +86,8 @@ module __Fortran_type_info
     integer(kind=int64) :: offset
     type(Value) :: characterLen ! for category == Character
     type(DerivedType), pointer :: derived ! for category == Derived
-    type(Value), pointer :: lenValue(:) ! (SIZE(derived%lenParameterKind))
-    type(Value), pointer :: bounds(:, :) ! (2, rank): lower, upper
+    type(Value), pointer, contiguous :: lenValue(:) ! (SIZE(derived%lenParameterKind))
+    type(Value), pointer, contiguous :: bounds(:, :) ! (2, rank): lower, upper
     type(__builtin_c_ptr) :: initialization
   end type
 
index a484c94..7d5c88e 100644 (file)
@@ -35,6 +35,7 @@ add_flang_library(FortranRuntime
   allocatable.cpp
   buffer.cpp
   complex-reduction.c
+  copy.cpp
   character.cpp
   connection.cpp
   derived.cpp
index addc1b7..f141229 100644 (file)
@@ -1,4 +1,4 @@
-//===-- runtime/allocatable.cpp ---------------------------------*- C++ -*-===//
+//===-- runtime/allocatable.cpp -------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
diff --git a/flang/runtime/copy.cpp b/flang/runtime/copy.cpp
new file mode 100644 (file)
index 0000000..458b8f0
--- /dev/null
@@ -0,0 +1,64 @@
+//===-- runtime/copy.cpp -------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "copy.h"
+#include "allocatable.h"
+#include "descriptor.h"
+#include "terminator.h"
+#include "type-info.h"
+#include <cstring>
+
+namespace Fortran::runtime {
+
+void CopyElement(const Descriptor &to, const SubscriptValue toAt[],
+    const Descriptor &from, const SubscriptValue fromAt[],
+    Terminator &terminator) {
+  char *toPtr{to.Element<char>(toAt)};
+  const char *fromPtr{from.Element<const char>(fromAt)};
+  RUNTIME_CHECK(terminator, to.ElementBytes() == from.ElementBytes());
+  std::memcpy(toPtr, fromPtr, to.ElementBytes());
+  if (const auto *addendum{to.Addendum()}) {
+    if (const auto *derived{addendum->derivedType()}) {
+      RUNTIME_CHECK(terminator,
+          from.Addendum() && derived == from.Addendum()->derivedType());
+      const Descriptor &componentDesc{derived->component.descriptor()};
+      const typeInfo::Component *component{
+          componentDesc.OffsetElement<typeInfo::Component>()};
+      std::size_t nComponents{componentDesc.Elements()};
+      for (std::size_t j{0}; j < nComponents; ++j, ++component) {
+        if (component->genre == typeInfo::Component::Genre::Allocatable ||
+            component->genre == typeInfo::Component::Genre::Automatic) {
+          Descriptor &toDesc{
+              *reinterpret_cast<Descriptor *>(toPtr + component->offset)};
+          if (toDesc.raw().base_addr != nullptr) {
+            toDesc.set_base_addr(nullptr);
+            RUNTIME_CHECK(terminator, toDesc.Allocate() == CFI_SUCCESS);
+            const Descriptor &fromDesc{*reinterpret_cast<const Descriptor *>(
+                fromPtr + component->offset)};
+            CopyArray(toDesc, fromDesc, terminator);
+          }
+        }
+      }
+    }
+  }
+}
+
+void CopyArray(
+    const Descriptor &to, const Descriptor &from, Terminator &terminator) {
+  std::size_t elements{to.Elements()};
+  RUNTIME_CHECK(terminator, elements == from.Elements());
+  SubscriptValue toAt[maxRank], fromAt[maxRank];
+  to.GetLowerBounds(toAt);
+  from.GetLowerBounds(fromAt);
+  while (elements-- > 0) {
+    CopyElement(to, toAt, from, fromAt, terminator);
+    to.IncrementSubscripts(toAt);
+    from.IncrementSubscripts(fromAt);
+  }
+}
+} // namespace Fortran::runtime
diff --git a/flang/runtime/copy.h b/flang/runtime/copy.h
new file mode 100644 (file)
index 0000000..6de4455
--- /dev/null
@@ -0,0 +1,28 @@
+//===-- runtime/copy.h -----------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Utilities that copy data in a type-aware fashion, allocating & duplicating
+// allocatable/automatic components of derived types along the way.
+
+#ifndef FORTRAN_RUNTIME_COPY_H_
+#define FORTRAN_RUNTIME_COPY_H_
+
+#include "descriptor.h"
+
+namespace Fortran::runtime {
+
+// Assigns to uninitialized storage.
+// Duplicates allocatable & automatic components.
+void CopyElement(const Descriptor &to, const SubscriptValue toAt[],
+    const Descriptor &from, const SubscriptValue fromAt[], Terminator &);
+
+// Copies data from one allocated descriptor's array to another.
+void CopyArray(const Descriptor &to, const Descriptor &from, Terminator &);
+
+} // namespace Fortran::runtime
+#endif // FORTRAN_RUNTIME_COPY_H_
index d86c136..5e03ad0 100644 (file)
@@ -246,10 +246,18 @@ public:
     return nullptr;
   }
 
-  void GetLowerBounds(SubscriptValue subscript[]) const {
+  int GetLowerBounds(SubscriptValue subscript[]) const {
     for (int j{0}; j < raw_.rank; ++j) {
       subscript[j] = GetDimension(j).LowerBound();
     }
+    return raw_.rank;
+  }
+
+  int GetShape(SubscriptValue subscript[]) const {
+    for (int j{0}; j < raw_.rank; ++j) {
+      subscript[j] = GetDimension(j).Extent();
+    }
+    return raw_.rank;
   }
 
   // When the passed subscript vector contains the last (or first)
index 2d036f5..c67da77 100644 (file)
@@ -106,5 +106,4 @@ void CheckIntegerKind(Terminator &terminator, int kind, const char *intrinsic) {
     terminator.Crash("%s: bad KIND=%d argument", intrinsic, kind);
   }
 }
-
 } // namespace Fortran::runtime
index ee8c439..d4a0708 100644 (file)
@@ -66,7 +66,8 @@ inline void PutContiguousConverted(TO *to, FROM *from, std::size_t count) {
   }
 }
 
-static inline std::int64_t GetInt64(const char *p, std::size_t bytes) {
+static inline std::int64_t GetInt64(
+    const char *p, std::size_t bytes, Terminator &terminator) {
   switch (bytes) {
   case 1:
     return *reinterpret_cast<const CppTypeFor<TypeCategory::Integer, 1> *>(p);
@@ -77,8 +78,7 @@ static inline std::int64_t GetInt64(const char *p, std::size_t bytes) {
   case 8:
     return *reinterpret_cast<const CppTypeFor<TypeCategory::Integer, 8> *>(p);
   default:
-    Terminator{__FILE__, __LINE__}.Crash(
-        "GetInt64: no case for %zd bytes", bytes);
+    terminator.Crash("GetInt64: no case for %zd bytes", bytes);
   }
 }
 
@@ -333,6 +333,5 @@ std::optional<std::pair<TypeCategory, int>> inline constexpr GetResultType(
   }
   return std::nullopt;
 }
-
 } // namespace Fortran::runtime
 #endif // FORTRAN_RUNTIME_TOOLS_H_
index 07a34c1..e7cd089 100644 (file)
 //
 //===----------------------------------------------------------------------===//
 
+// Implements the transformational intrinsic functions of Fortran 2018 that
+// rearrange or duplicate data without (much) regard to type.  These are
+// CSHIFT, EOSHIFT, PACK, RESHAPE, SPREAD, TRANSPOSE, and UNPACK.
+//
+// Many of these are defined in the 2018 standard with text that makes sense
+// only if argument arrays have lower bounds of one.  Rather than interpret
+// these cases as implying a hidden constraint, these implementations
+// work with arbitrary lower bounds.  This may be technically an extension
+// of the standard but it more likely to conform with its intent.
+
 #include "transformational.h"
+#include "copy.h"
 #include "terminator.h"
 #include "tools.h"
 #include <algorithm>
-#include <cinttypes>
 
 namespace Fortran::runtime {
 
+// Utility for CSHIFT & EOSHIFT rank > 1 cases that determines the shift count
+// for each of the vector sections of the result.
+class ShiftControl {
+public:
+  ShiftControl(const Descriptor &s, Terminator &t, int dim)
+      : shift_{s}, terminator_{t}, shiftRank_{s.rank()}, dim_{dim} {}
+  void Init(const Descriptor &source) {
+    int rank{source.rank()};
+    RUNTIME_CHECK(terminator_, shiftRank_ == 0 || shiftRank_ == rank - 1);
+    auto catAndKind{shift_.type().GetCategoryAndKind()};
+    RUNTIME_CHECK(
+        terminator_, catAndKind && catAndKind->first == TypeCategory::Integer);
+    shiftElemLen_ = catAndKind->second;
+    if (shiftRank_ > 0) {
+      int k{0};
+      for (int j{0}; j < rank; ++j) {
+        if (j + 1 != dim_) {
+          const Dimension &shiftDim{shift_.GetDimension(k)};
+          lb_[k++] = shiftDim.LowerBound();
+          RUNTIME_CHECK(terminator_,
+              shiftDim.Extent() == source.GetDimension(j).Extent());
+        }
+      }
+    } else {
+      shiftCount_ =
+          GetInt64(shift_.OffsetElement<char>(), shiftElemLen_, terminator_);
+    }
+  }
+  SubscriptValue GetShift(const SubscriptValue resultAt[]) const {
+    if (shiftRank_ > 0) {
+      SubscriptValue shiftAt[maxRank];
+      int k{0};
+      for (int j{0}; j < shiftRank_ + 1; ++j) {
+        if (j + 1 != dim_) {
+          shiftAt[k] = lb_[k] + resultAt[j] - 1;
+          ++k;
+        }
+      }
+      return GetInt64(
+          shift_.Element<char>(shiftAt), shiftElemLen_, terminator_);
+    } else {
+      return shiftCount_; // invariant count extracted in Init()
+    }
+  }
+
+private:
+  const Descriptor &shift_;
+  Terminator &terminator_;
+  int shiftRank_;
+  int dim_;
+  SubscriptValue lb_[maxRank];
+  std::size_t shiftElemLen_;
+  SubscriptValue shiftCount_{};
+};
+
+// Fill an EOSHIFT result with default boundary values
+static void DefaultInitialize(
+    const Descriptor &result, Terminator &terminator) {
+  auto catAndKind{result.type().GetCategoryAndKind()};
+  RUNTIME_CHECK(
+      terminator, catAndKind && catAndKind->first != TypeCategory::Derived);
+  std::size_t elementLen{result.ElementBytes()};
+  std::size_t bytes{result.Elements() * elementLen};
+  if (catAndKind->first == TypeCategory::Character) {
+    switch (int kind{catAndKind->second}) {
+    case 1:
+      std::fill_n(result.OffsetElement<char>(), bytes, ' ');
+      break;
+    case 2:
+      std::fill_n(result.OffsetElement<char16_t>(), bytes / 2,
+          static_cast<char16_t>(' '));
+      break;
+    case 4:
+      std::fill_n(result.OffsetElement<char32_t>(), bytes / 4,
+          static_cast<char32_t>(' '));
+      break;
+    default:
+      terminator.Crash("EOSHIFT: bad CHARACTER kind %d", kind);
+    }
+  } else {
+    std::memset(result.raw().base_addr, 0, bytes);
+  }
+}
+
+static inline std::size_t AllocateResult(Descriptor &result,
+    const Descriptor &source, int rank, const SubscriptValue extent[],
+    Terminator &terminator, const char *function) {
+  std::size_t elementLen{source.ElementBytes()};
+  const DescriptorAddendum *sourceAddendum{source.Addendum()};
+  result.Establish(source.type(), elementLen, nullptr, rank, extent,
+      CFI_attribute_allocatable, sourceAddendum != nullptr);
+  if (sourceAddendum) {
+    *result.Addendum() = *sourceAddendum;
+  }
+  for (int j{0}; j < rank; ++j) {
+    result.GetDimension(j).SetBounds(1, extent[j]);
+  }
+  if (int stat{result.Allocate()}) {
+    terminator.Crash(
+        "%s: Could not allocate memory for result (stat=%d)", function, stat);
+  }
+  return elementLen;
+}
+
+extern "C" {
+
+// CSHIFT of rank > 1
+void RTNAME(Cshift)(Descriptor &result, const Descriptor &source,
+    const Descriptor &shift, int dim, const char *sourceFile, int line) {
+  Terminator terminator{sourceFile, line};
+  int rank{source.rank()};
+  RUNTIME_CHECK(terminator, rank > 1);
+  RUNTIME_CHECK(terminator, dim >= 1 && dim <= rank);
+  ShiftControl shiftControl{shift, terminator, dim};
+  shiftControl.Init(source);
+  SubscriptValue extent[maxRank];
+  source.GetShape(extent);
+  AllocateResult(result, source, rank, extent, terminator, "CSHIFT");
+  SubscriptValue resultAt[maxRank];
+  for (int j{0}; j < rank; ++j) {
+    resultAt[j] = 1;
+  }
+  SubscriptValue sourceLB[maxRank];
+  source.GetLowerBounds(sourceLB);
+  SubscriptValue dimExtent{extent[dim - 1]};
+  SubscriptValue dimLB{sourceLB[dim - 1]};
+  SubscriptValue &resDim{resultAt[dim - 1]};
+  for (std::size_t n{result.Elements()}; n > 0; n -= dimExtent) {
+    SubscriptValue shiftCount{shiftControl.GetShift(resultAt)};
+    SubscriptValue sourceAt[maxRank];
+    for (int j{0}; j < rank; ++j) {
+      sourceAt[j] = sourceLB[j] + resultAt[j] - 1;
+    }
+    SubscriptValue &sourceDim{sourceAt[dim - 1]};
+    sourceDim = dimLB + shiftCount % dimExtent;
+    if (shiftCount < 0) {
+      sourceDim += dimExtent;
+    }
+    for (resDim = 1; resDim <= dimExtent; ++resDim) {
+      CopyElement(result, resultAt, source, sourceAt, terminator);
+      if (++sourceDim == dimLB + dimExtent) {
+        sourceDim = dimLB;
+      }
+    }
+    result.IncrementSubscripts(resultAt);
+  }
+}
+
+// CSHIFT of vector
+void RTNAME(CshiftVector)(Descriptor &result, const Descriptor &source,
+    std::int64_t shift, const char *sourceFile, int line) {
+  Terminator terminator{sourceFile, line};
+  RUNTIME_CHECK(terminator, source.rank() == 1);
+  const Dimension &sourceDim{source.GetDimension(0)};
+  SubscriptValue extent{sourceDim.Extent()};
+  AllocateResult(result, source, 1, &extent, terminator, "CSHIFT");
+  SubscriptValue lb{sourceDim.LowerBound()};
+  for (SubscriptValue j{0}; j < extent; ++j) {
+    SubscriptValue resultAt{1 + j};
+    SubscriptValue sourceAt{lb + (j + shift) % extent};
+    CopyElement(result, &resultAt, source, &sourceAt, terminator);
+  }
+}
+
+// EOSHIFT of rank > 1
+void RTNAME(Eoshift)(Descriptor &result, const Descriptor &source,
+    const Descriptor &shift, const Descriptor *boundary, int dim,
+    const char *sourceFile, int line) {
+  Terminator terminator{sourceFile, line};
+  SubscriptValue extent[maxRank];
+  int rank{source.GetShape(extent)};
+  RUNTIME_CHECK(terminator, rank > 1);
+  RUNTIME_CHECK(terminator, dim >= 1 && dim <= rank);
+  std::size_t elementLen{
+      AllocateResult(result, source, rank, extent, terminator, "EOSHIFT")};
+  int boundaryRank{-1};
+  if (boundary) {
+    boundaryRank = boundary->rank();
+    RUNTIME_CHECK(terminator, boundaryRank == 0 || boundaryRank == rank - 1);
+    RUNTIME_CHECK(terminator,
+        boundary->type() == source.type() &&
+            boundary->ElementBytes() == elementLen);
+    if (boundaryRank > 0) {
+      int k{0};
+      for (int j{0}; j < rank; ++j) {
+        if (j != dim - 1) {
+          RUNTIME_CHECK(
+              terminator, boundary->GetDimension(k).Extent() == extent[j]);
+          ++k;
+        }
+      }
+    }
+  }
+  ShiftControl shiftControl{shift, terminator, dim};
+  shiftControl.Init(source);
+  SubscriptValue resultAt[maxRank];
+  for (int j{0}; j < rank; ++j) {
+    resultAt[j] = 1;
+  }
+  if (!boundary) {
+    DefaultInitialize(result, terminator);
+  }
+  SubscriptValue sourceLB[maxRank];
+  source.GetLowerBounds(sourceLB);
+  SubscriptValue boundaryAt[maxRank];
+  if (boundaryRank > 0) {
+    boundary->GetLowerBounds(boundaryAt);
+  }
+  SubscriptValue dimExtent{extent[dim - 1]};
+  SubscriptValue dimLB{sourceLB[dim - 1]};
+  SubscriptValue &resDim{resultAt[dim - 1]};
+  for (std::size_t n{result.Elements()}; n > 0; n -= dimExtent) {
+    SubscriptValue shiftCount{shiftControl.GetShift(resultAt)};
+    SubscriptValue sourceAt[maxRank];
+    for (int j{0}; j < rank; ++j) {
+      sourceAt[j] = sourceLB[j] + resultAt[j] - 1;
+    }
+    SubscriptValue &sourceDim{sourceAt[dim - 1]};
+    sourceDim = dimLB + shiftCount;
+    for (resDim = 1; resDim <= dimExtent; ++resDim) {
+      if (sourceDim >= dimLB && sourceDim < dimLB + dimExtent) {
+        CopyElement(result, resultAt, source, sourceAt, terminator);
+      } else if (boundary) {
+        CopyElement(result, resultAt, *boundary, boundaryAt, terminator);
+      }
+      ++sourceDim;
+    }
+    result.IncrementSubscripts(resultAt);
+    if (boundaryRank > 0) {
+      boundary->IncrementSubscripts(boundaryAt);
+    }
+  }
+}
+
+// EOSHIFT of vector
+void RTNAME(EoshiftVector)(Descriptor &result, const Descriptor &source,
+    std::int64_t shift, const Descriptor *boundary, const char *sourceFile,
+    int line) {
+  Terminator terminator{sourceFile, line};
+  RUNTIME_CHECK(terminator, source.rank() == 1);
+  SubscriptValue extent{source.GetDimension(0).Extent()};
+  std::size_t elementLen{
+      AllocateResult(result, source, 1, &extent, terminator, "EOSHIFT")};
+  std::optional<int> blankFill; // kind of character
+  if (boundary) {
+    RUNTIME_CHECK(terminator, boundary->rank() == 0);
+    RUNTIME_CHECK(terminator,
+        boundary->type() == source.type() &&
+            boundary->ElementBytes() == elementLen);
+  }
+  if (!boundary) {
+    DefaultInitialize(result, terminator);
+  }
+  SubscriptValue lb{source.GetDimension(0).LowerBound()};
+  for (SubscriptValue j{1}; j <= extent; ++j) {
+    SubscriptValue sourceAt{lb + j - 1 + shift};
+    if (sourceAt >= lb && sourceAt < lb + extent) {
+      CopyElement(result, &j, source, &sourceAt, terminator);
+    }
+  }
+}
+
+// PACK
+void RTNAME(Pack)(Descriptor &result, const Descriptor &source,
+    const Descriptor &mask, const Descriptor *vector, const char *sourceFile,
+    int line) {
+  Terminator terminator{sourceFile, line};
+  CheckConformability(source, mask, terminator, "PACK", "ARRAY=", "MASK=");
+  auto maskType{mask.type().GetCategoryAndKind()};
+  RUNTIME_CHECK(
+      terminator, maskType && maskType->first == TypeCategory::Logical);
+  SubscriptValue trues{0};
+  if (mask.rank() == 0) {
+    if (IsLogicalElementTrue(mask, nullptr)) {
+      trues = source.Elements();
+    }
+  } else {
+    SubscriptValue maskAt[maxRank];
+    mask.GetLowerBounds(maskAt);
+    for (std::size_t n{mask.Elements()}; n > 0; --n) {
+      if (IsLogicalElementTrue(mask, maskAt)) {
+        ++trues;
+      }
+      mask.IncrementSubscripts(maskAt);
+    }
+  }
+  SubscriptValue extent{trues};
+  if (vector) {
+    RUNTIME_CHECK(terminator, vector->rank() == 1);
+    RUNTIME_CHECK(terminator,
+        source.type() == vector->type() &&
+            source.ElementBytes() == vector->ElementBytes());
+    extent = vector->GetDimension(0).Extent();
+    RUNTIME_CHECK(terminator, extent >= trues);
+  }
+  AllocateResult(result, source, 1, &extent, terminator, "PACK");
+  SubscriptValue sourceAt[maxRank], resultAt{1};
+  source.GetLowerBounds(sourceAt);
+  if (mask.rank() == 0) {
+    if (IsLogicalElementTrue(mask, nullptr)) {
+      for (SubscriptValue n{trues}; n > 0; --n) {
+        CopyElement(result, &resultAt, source, sourceAt, terminator);
+        ++resultAt;
+        source.IncrementSubscripts(sourceAt);
+      }
+    }
+  } else {
+    SubscriptValue maskAt[maxRank];
+    mask.GetLowerBounds(maskAt);
+    for (std::size_t n{source.Elements()}; n > 0; --n) {
+      if (IsLogicalElementTrue(mask, maskAt)) {
+        CopyElement(result, &resultAt, source, sourceAt, terminator);
+        ++resultAt;
+      }
+      source.IncrementSubscripts(sourceAt);
+      mask.IncrementSubscripts(maskAt);
+    }
+  }
+  if (vector) {
+    SubscriptValue vectorAt{
+        vector->GetDimension(0).LowerBound() + resultAt - 1};
+    for (; resultAt <= extent; ++resultAt, ++vectorAt) {
+      CopyElement(result, &resultAt, *vector, &vectorAt, terminator);
+    }
+  }
+}
+
 // F2018 16.9.163
 OwningPtr<Descriptor> RTNAME(Reshape)(const Descriptor &source,
-    const Descriptor &shape, const Descriptor *pad, const Descriptor *order) {
+    const Descriptor &shape, const Descriptor *pad, const Descriptor *order,
+    const char *sourceFile, int line) {
   // Compute and check the rank of the result.
-  Terminator terminator{__FILE__, __LINE__};
+  Terminator terminator{sourceFile, line};
   RUNTIME_CHECK(terminator, shape.rank() == 1);
   RUNTIME_CHECK(terminator, shape.type().IsInteger());
   SubscriptValue resultRank{shape.GetDimension(0).Extent()};
@@ -33,8 +371,8 @@ OwningPtr<Descriptor> RTNAME(Reshape)(const Descriptor &source,
   SubscriptValue shapeSubscript{shape.GetDimension(0).LowerBound()};
   for (SubscriptValue j{0}; j < resultRank; ++j, ++shapeSubscript) {
     lowerBound[j] = 1;
-    resultExtent[j] =
-        GetInt64(shape.Element<char>(&shapeSubscript), shapeElementBytes);
+    resultExtent[j] = GetInt64(
+        shape.Element<char>(&shapeSubscript), shapeElementBytes, terminator);
     RUNTIME_CHECK(terminator, resultExtent[j] >= 0);
     resultElements *= resultExtent[j];
   }
@@ -59,8 +397,8 @@ OwningPtr<Descriptor> RTNAME(Reshape)(const Descriptor &source,
     std::uint64_t values{0};
     SubscriptValue orderSubscript{order->GetDimension(0).LowerBound()};
     for (SubscriptValue j{0}; j < resultRank; ++j, ++orderSubscript) {
-      auto k{GetInt64(
-          order->OffsetElement<char>(orderSubscript), shapeElementBytes)};
+      auto k{GetInt64(order->OffsetElement<char>(orderSubscript),
+          shapeElementBytes, terminator)};
       RUNTIME_CHECK(
           terminator, k >= 1 && k <= resultRank && !((values >> k) & 1));
       values |= std::uint64_t{1} << k;
@@ -109,8 +447,7 @@ OwningPtr<Descriptor> RTNAME(Reshape)(const Descriptor &source,
   std::size_t resultElement{0};
   std::size_t elementsFromSource{std::min(resultElements, sourceElements)};
   for (; resultElement < elementsFromSource; ++resultElement) {
-    std::memcpy(result->Element<void>(resultSubscript),
-        source.Element<const void>(sourceSubscript), elementBytes);
+    CopyElement(*result, resultSubscript, source, sourceSubscript, terminator);
     source.IncrementSubscripts(sourceSubscript);
     result->IncrementSubscripts(resultSubscript, dimOrder);
   }
@@ -119,8 +456,7 @@ OwningPtr<Descriptor> RTNAME(Reshape)(const Descriptor &source,
     SubscriptValue padSubscript[maxRank];
     pad->GetLowerBounds(padSubscript);
     for (; resultElement < resultElements; ++resultElement) {
-      std::memcpy(result->Element<void>(resultSubscript),
-          pad->Element<const void>(padSubscript), elementBytes);
+      CopyElement(*result, resultSubscript, *pad, padSubscript, terminator);
       pad->IncrementSubscripts(padSubscript);
       result->IncrementSubscripts(resultSubscript, dimOrder);
     }
@@ -128,4 +464,94 @@ OwningPtr<Descriptor> RTNAME(Reshape)(const Descriptor &source,
 
   return result;
 }
+
+// SPREAD
+void RTNAME(Spread)(Descriptor &result, const Descriptor &source, int dim,
+    std::int64_t ncopies, const char *sourceFile, int line) {
+  Terminator terminator{sourceFile, line};
+  int rank{source.rank() + 1};
+  RUNTIME_CHECK(terminator, rank <= maxRank);
+  ncopies = std::max<std::int64_t>(ncopies, 0);
+  SubscriptValue extent[maxRank];
+  int k{0};
+  for (int j{0}; j < rank; ++j) {
+    extent[j] = j == dim - 1 ? ncopies : source.GetDimension(k++).Extent();
+  }
+  AllocateResult(result, source, rank, extent, terminator, "SPREAD");
+  SubscriptValue resultAt[maxRank];
+  for (int j{0}; j < rank; ++j) {
+    resultAt[j] = 1;
+  }
+  SubscriptValue &resultDim{resultAt[dim - 1]};
+  SubscriptValue sourceAt[maxRank];
+  source.GetLowerBounds(sourceAt);
+  for (std::size_t n{result.Elements()}; n > 0; n -= ncopies) {
+    for (resultDim = 1; resultDim <= ncopies; ++resultDim) {
+      CopyElement(result, resultAt, source, sourceAt, terminator);
+    }
+    result.IncrementSubscripts(resultAt);
+    source.IncrementSubscripts(sourceAt);
+  }
+}
+
+// TRANSPOSE
+void RTNAME(Transpose)(Descriptor &result, const Descriptor &matrix,
+    const char *sourceFile, int line) {
+  Terminator terminator{sourceFile, line};
+  RUNTIME_CHECK(terminator, matrix.rank() == 2);
+  SubscriptValue extent[2]{
+      matrix.GetDimension(1).Extent(), matrix.GetDimension(0).Extent()};
+  AllocateResult(result, matrix, 2, extent, terminator, "TRANSPOSE");
+  SubscriptValue resultAt[2]{1, 1};
+  SubscriptValue matrixLB[2];
+  matrix.GetLowerBounds(matrixLB);
+  for (std::size_t n{result.Elements()}; n-- > 0;
+       result.IncrementSubscripts(resultAt)) {
+    SubscriptValue matrixAt[2]{
+        matrixLB[0] + resultAt[1] - 1, matrixLB[1] + resultAt[0] - 1};
+    CopyElement(result, resultAt, matrix, matrixAt, terminator);
+  }
+}
+
+// UNPACK
+void RTNAME(Unpack)(Descriptor &result, const Descriptor &vector,
+    const Descriptor &mask, const Descriptor &field, const char *sourceFile,
+    int line) {
+  Terminator terminator{sourceFile, line};
+  RUNTIME_CHECK(terminator, vector.rank() == 1);
+  int rank{mask.rank()};
+  RUNTIME_CHECK(terminator, rank > 0);
+  SubscriptValue extent[maxRank];
+  mask.GetShape(extent);
+  CheckConformability(mask, field, terminator, "UNPACK", "MASK=", "FIELD=");
+  std::size_t elementLen{
+      AllocateResult(result, field, rank, extent, terminator, "UNPACK")};
+  RUNTIME_CHECK(terminator,
+      vector.type() == field.type() && vector.ElementBytes() == elementLen);
+  SubscriptValue resultAt[maxRank], maskAt[maxRank], fieldAt[maxRank],
+      vectorAt{vector.GetDimension(0).LowerBound()};
+  for (int j{0}; j < rank; ++j) {
+    resultAt[j] = 1;
+  }
+  mask.GetLowerBounds(maskAt);
+  field.GetLowerBounds(fieldAt);
+  SubscriptValue vectorLeft{vector.GetDimension(0).Extent()};
+  for (std::size_t n{result.Elements()}; n-- > 0;) {
+    if (IsLogicalElementTrue(mask, maskAt)) {
+      if (vectorLeft-- == 0) {
+        terminator.Crash("UNPACK: VECTOR= argument has fewer elements than "
+                         "MASK= has .TRUE. entries");
+      }
+      CopyElement(result, resultAt, vector, &vectorAt, terminator);
+      ++vectorAt;
+    } else {
+      CopyElement(result, resultAt, field, fieldAt, terminator);
+    }
+    result.IncrementSubscripts(resultAt);
+    mask.IncrementSubscripts(maskAt);
+    field.IncrementSubscripts(fieldAt);
+  }
+}
+
+} // extern "C"
 } // namespace Fortran::runtime
index 1994fca..85d2ae5 100644 (file)
@@ -6,6 +6,14 @@
 //
 //===----------------------------------------------------------------------===//
 
+// Defines the API for the type-independent transformational intrinsic functions
+// that rearrange data in arrays: CSHIFT, EOSHIFT, PACK, RESHAPE, SPREAD,
+// TRANSPOSE, and UNPACK.
+// These are naive allocating implementations; optimized forms that manipulate
+// pointer descriptors or that supply functional views of arrays remain to
+// be defined and may instead be part of lowering (see docs/ArrayComposition.md)
+// for details).
+
 #ifndef FORTRAN_RUNTIME_TRANSFORMATIONAL_H_
 #define FORTRAN_RUNTIME_TRANSFORMATIONAL_H_
 
 #include "memory.h"
 
 namespace Fortran::runtime {
+extern "C" {
+
+void RTNAME(Cshift)(Descriptor &result, const Descriptor &source,
+    const Descriptor &shift, int dim = 1, const char *sourceFile = nullptr,
+    int line = 0);
+void RTNAME(CshiftVector)(Descriptor &result, const Descriptor &source,
+    std::int64_t shift, const char *sourceFile = nullptr, int line = 0);
+
+void RTNAME(Eoshift)(Descriptor &result, const Descriptor &source,
+    const Descriptor &shift, const Descriptor *boundary = nullptr, int dim = 1,
+    const char *sourceFile = nullptr, int line = 0);
+void RTNAME(EoshiftVector)(Descriptor &result, const Descriptor &source,
+    std::int64_t shift, const Descriptor *boundary = nullptr,
+    const char *sourceFile = nullptr, int line = 0);
+
+void RTNAME(Pack)(Descriptor &result, const Descriptor &source,
+    const Descriptor &mask, const Descriptor *vector = nullptr,
+    const char *sourceFile = nullptr, int line = 0);
 
+// TODO: redo API
 OwningPtr<Descriptor> RTNAME(Reshape)(const Descriptor &source,
     const Descriptor &shape, const Descriptor *pad = nullptr,
-    const Descriptor *order = nullptr);
-}
+    const Descriptor *order = nullptr, const char *sourceFile = nullptr,
+    int line = 0);
+
+void RTNAME(Spread)(Descriptor &result, const Descriptor &source, int dim,
+    std::int64_t ncopies, const char *sourceFile = nullptr, int line = 0);
+
+void RTNAME(Transpose)(Descriptor &result, const Descriptor &matrix,
+    const char *sourceFile = nullptr, int line = 0);
+
+void RTNAME(Unpack)(Descriptor &result, const Descriptor &vector,
+    const Descriptor &mask, const Descriptor &field,
+    const char *sourceFile = nullptr, int line = 0);
+
+} // extern "C"
+} // namespace Fortran::runtime
 #endif // FORTRAN_RUNTIME_TRANSFORMATIONAL_H_
index 4f933e8..c83a5f2 100644 (file)
@@ -27,12 +27,13 @@ public:
   // It includes all of the ancestor types' bindings, if any, first,
   // with any overrides from descendants already applied to them.  Local
   // bindings then follow in alphabetic order of binding name.
-  StaticDescriptor<1> binding; // TYPE(BINDING), DIMENSION(:), POINTER
+  StaticDescriptor<1, true>
+      binding; // TYPE(BINDING), DIMENSION(:), POINTER, CONTIGUOUS
 
   StaticDescriptor<0> name; // CHARACTER(:), POINTER
 
   std::uint64_t sizeInBytes{0};
-  StaticDescriptor<0> parent; // TYPE(DERIVEDTYPE), POINTER
+  StaticDescriptor<0, true> parent; // TYPE(DERIVEDTYPE), POINTER
 
   // Instantiations of a parameterized derived type with KIND type
   // parameters will point this data member to the description of
@@ -40,7 +41,7 @@ public:
   // module via use association.  The original uninstantiated derived
   // type description will point to itself.  Derived types that have
   // no KIND type parameters will have a null pointer here.
-  StaticDescriptor<0> uninstantiated; // TYPE(DERIVEDTYPE), POINTER
+  StaticDescriptor<0, true> uninstantiated; // TYPE(DERIVEDTYPE), POINTER
 
   // TODO: flags for SEQUENCE, BIND(C), any PRIVATE component(? see 7.5.2)
   std::uint64_t typeHash{0};
@@ -52,14 +53,16 @@ public:
   // This array of local data components includes the parent component.
   // Components are in alphabetic order.
   // It does not include procedure pointer components.
-  StaticDescriptor<1, true> component; // TYPE(COMPONENT), POINTER, DIMENSION(:)
+  StaticDescriptor<1, true>
+      component; // TYPE(COMPONENT), POINTER, DIMENSION(:), CONTIGUOUS
 
   // Procedure pointer components
-  StaticDescriptor<1, true> procPtr; // TYPE(PROCPTR), POINTER, DIMENSION(:)
+  StaticDescriptor<1, true>
+      procPtr; // TYPE(PROCPTR), POINTER, DIMENSION(:), CONTIGUOUS
 
   // Does not include special bindings from ancestral types.
   StaticDescriptor<1, true>
-      special; // TYPE(SPECIALBINDING), POINTER, DIMENSION(:)
+      special; // TYPE(SPECIALBINDING), POINTER, DIMENSION(:), CONTIGUOUS
 
   std::size_t LenParameters() const {
     return lenParameterKind.descriptor().Elements();
@@ -95,8 +98,10 @@ struct Component {
   std::uint64_t offset{0};
   Value characterLen; // for TypeCategory::Character
   StaticDescriptor<0, true> derivedType; // TYPE(DERIVEDTYPE), POINTER
-  StaticDescriptor<1, true> lenValue; // TYPE(VALUE), POINTER, DIMENSION(:)
-  StaticDescriptor<2, true> bounds; // TYPE(VALUE), POINTER, DIMENSION(2,:)
+  StaticDescriptor<1, true>
+      lenValue; // TYPE(VALUE), POINTER, DIMENSION(:), CONTIGUOUS
+  StaticDescriptor<2, true>
+      bounds; // TYPE(VALUE), POINTER, DIMENSION(2,:), CONTIGUOUS
   char *initialization{nullptr}; // for Genre::Data and Pointer
   // TODO: cobounds
   // TODO: `PRIVATE` attribute
index a51acdb..c3aa8f4 100644 (file)
@@ -52,7 +52,8 @@ int main() {
   MATCH(2, pad.GetDimension(1).Extent());
   MATCH(3, pad.GetDimension(2).Extent());
 
-  auto result{RTNAME(Reshape)(*source, *shape, &pad)};
+  auto result{
+      RTNAME(Reshape)(*source, *shape, &pad, nullptr, __FILE__, __LINE__)};
   TEST(result.get() != nullptr);
   result->Check();
   MATCH(sizeof(std::int32_t), result->ElementBytes());
index 3d45cf6..13bfadf 100644 (file)
@@ -2,6 +2,7 @@ add_flang_unittest(FlangRuntimeTests
   CharacterTest.cpp
   CrashHandlerFixture.cpp
   Format.cpp
+  ListInputTest.cpp
   Matmul.cpp
   MiscIntrinsic.cpp
   Namelist.cpp
@@ -10,7 +11,7 @@ add_flang_unittest(FlangRuntimeTests
   Random.cpp
   Reduction.cpp
   RuntimeCrashTest.cpp
-  ListInputTest.cpp
+  Transformational.cpp
 )
 
 target_link_libraries(FlangRuntimeTests
index ae9e7a8..1f0c756 100644 (file)
@@ -27,7 +27,7 @@ TEST(Matmul, Basic) {
       std::vector<int>{3, 2}, std::vector<std::int16_t>{6, 7, 8, 9, 10, 11})};
   auto v{MakeArray<TypeCategory::Integer, 8>(
       std::vector<int>{2}, std::vector<std::int64_t>{-1, -2})};
-  StaticDescriptor<2> statDesc;
+  StaticDescriptor<2, true> statDesc;
   Descriptor &result{statDesc.descriptor()};
 
   RTNAME(Matmul)(result, *x, *y, __FILE__, __LINE__);
index fc38cee..77eec4e 100644 (file)
@@ -34,7 +34,7 @@ TEST(NamelistTests, BasicSanity) {
   static constexpr int numLines{12};
   static constexpr int lineLength{32};
   static char buffer[numLines][lineLength];
-  StaticDescriptor<1> statDescs[1];
+  StaticDescriptor<1, true> statDescs[1];
   Descriptor &internalDesc{statDescs[0].descriptor()};
   SubscriptValue extent[]{numLines};
   internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength,
@@ -136,7 +136,7 @@ TEST(NamelistTests, Subscripts) {
   const NamelistGroup::Item items[]{{"a", *aDesc}};
   const NamelistGroup group{"justa", 1, items};
   static char t1[]{"&justa A(0,1:-1:-2)=1 2/"};
-  StaticDescriptor<1> statDescs[2];
+  StaticDescriptor<1, true> statDescs[2];
   Descriptor &internalDesc{statDescs[0].descriptor()};
   internalDesc.Establish(TypeCode{CFI_type_char},
       /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
index 5a2c6fb..4c01cf4 100644 (file)
@@ -1,4 +1,4 @@
-//===-- flang/unittests/RuntimeGTest/Reductions.cpp -------------*- C++ -*-===//
+//===-- flang/unittests/RuntimeGTest/Reductions.cpp -----------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -34,7 +34,7 @@ TEST(Reductions, DimMaskProductInt4) {
       shape, std::vector<std::int32_t>{1, 2, 3, 4, 5, 6})};
   auto mask{MakeArray<TypeCategory::Logical, 1>(
       shape, std::vector<bool>{true, false, false, true, true, true})};
-  StaticDescriptor<1> statDesc;
+  StaticDescriptor<1, true> statDesc;
   Descriptor &prod{statDesc.descriptor()};
   RTNAME(ProductDim)(prod, *array, 1, __FILE__, __LINE__, &*mask);
   EXPECT_EQ(prod.rank(), 1);
@@ -66,7 +66,7 @@ TEST(Reductions, DoubleMaxMinNorm2) {
   double norm2Error{
       std::abs(naiveNorm2 - RTNAME(Norm2_8)(*array, __FILE__, __LINE__))};
   EXPECT_LE(norm2Error, 0.000001 * naiveNorm2);
-  StaticDescriptor<2> statDesc;
+  StaticDescriptor<2, true> statDesc;
   Descriptor &loc{statDesc.descriptor()};
   RTNAME(Maxloc)
   (loc, *array, /*KIND=*/8, __FILE__, __LINE__, /*MASK=*/nullptr,
@@ -146,7 +146,7 @@ TEST(Reductions, Character) {
   std::vector<int> shape{2, 3};
   auto array{MakeArray<TypeCategory::Character, 1>(shape,
       std::vector<std::string>{"abc", "def", "ghi", "jkl", "mno", "abc"}, 3)};
-  StaticDescriptor<1> statDesc[2];
+  StaticDescriptor<1, true> statDesc[2];
   Descriptor &res{statDesc[0].descriptor()};
   RTNAME(MaxvalCharacter)(res, *array, __FILE__, __LINE__);
   EXPECT_EQ(res.rank(), 0);
@@ -245,7 +245,7 @@ TEST(Reductions, Logical) {
   EXPECT_EQ(RTNAME(Any)(*array, __FILE__, __LINE__), true);
   EXPECT_EQ(RTNAME(Parity)(*array, __FILE__, __LINE__), false);
   EXPECT_EQ(RTNAME(Count)(*array, __FILE__, __LINE__), 2);
-  StaticDescriptor<2> statDesc[2];
+  StaticDescriptor<2, true> statDesc[2];
   Descriptor &res{statDesc[0].descriptor()};
   RTNAME(AllDim)(res, *array, /*DIM=*/1, __FILE__, __LINE__);
   EXPECT_EQ(res.rank(), 1);
@@ -344,7 +344,7 @@ TEST(Reductions, FindlocNumeric) {
           std::numeric_limits<double>::quiet_NaN(),
           std::numeric_limits<double>::infinity()})};
   ASSERT_EQ(realArray->ElementBytes(), sizeof(double));
-  StaticDescriptor<2> statDesc[2];
+  StaticDescriptor<2, true> statDesc[2];
   Descriptor &res{statDesc[0].descriptor()};
   // Find the first zero
   Descriptor &target{statDesc[1].descriptor()};
diff --git a/flang/unittests/RuntimeGTest/Transformational.cpp b/flang/unittests/RuntimeGTest/Transformational.cpp
new file mode 100644 (file)
index 0000000..00495fc
--- /dev/null
@@ -0,0 +1,203 @@
+//===-- flang/unittests/RuntimeGTest/Transformational.cpp -----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "../../runtime/transformational.h"
+#include "gtest/gtest.h"
+#include "tools.h"
+#include "../../runtime/type-code.h"
+
+using namespace Fortran::runtime;
+using Fortran::common::TypeCategory;
+
+TEST(Transformational, Shifts) {
+  // ARRAY  1 3 5
+  //        2 4 6
+  auto array{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{2, 3}, std::vector<std::int32_t>{1, 2, 3, 4, 5, 6})};
+  array->GetDimension(0).SetLowerBound(0); // shouldn't matter
+  array->GetDimension(1).SetLowerBound(-1);
+  StaticDescriptor<2, true> statDesc;
+  Descriptor &result{statDesc.descriptor()};
+
+  auto shift3{MakeArray<TypeCategory::Integer, 8>(
+      std::vector<int>{3}, std::vector<std::int64_t>{1, -1, 2})};
+  RTNAME(Cshift)(result, *array, *shift3, 1, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), array->type());
+  EXPECT_EQ(result.rank(), 2);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 2);
+  EXPECT_EQ(result.GetDimension(1).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(1).Extent(), 3);
+  EXPECT_EQ(result.type(), (TypeCode{TypeCategory::Integer, 4}));
+  static std::int32_t cshiftExpect1[6]{2, 1, 4, 3, 5, 6};
+  for (int j{0}; j < 6; ++j) {
+    EXPECT_EQ(
+        *result.ZeroBasedIndexedElement<std::int32_t>(j), cshiftExpect1[j]);
+  }
+  result.Destroy();
+
+  auto shift2{MakeArray<TypeCategory::Integer, 1>(
+      std::vector<int>{2}, std::vector<std::int8_t>{1, -1})};
+  shift2->GetDimension(0).SetLowerBound(-1); // shouldn't matter
+  shift2->GetDimension(1).SetLowerBound(2);
+  RTNAME(Cshift)(result, *array, *shift2, 2, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), array->type());
+  EXPECT_EQ(result.rank(), 2);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 2);
+  EXPECT_EQ(result.GetDimension(1).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(1).Extent(), 3);
+  EXPECT_EQ(result.type(), (TypeCode{TypeCategory::Integer, 4}));
+  static std::int32_t cshiftExpect2[6]{3, 6, 5, 2, 1, 4};
+  for (int j{0}; j < 6; ++j) {
+    EXPECT_EQ(
+        *result.ZeroBasedIndexedElement<std::int32_t>(j), cshiftExpect2[j]);
+  }
+  result.Destroy();
+
+  auto boundary{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{3}, std::vector<std::int32_t>{-1, -2, -3})};
+  boundary->GetDimension(0).SetLowerBound(9); // shouldn't matter
+  RTNAME(Eoshift)(result, *array, *shift3, &*boundary, 1, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), array->type());
+  EXPECT_EQ(result.rank(), 2);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 2);
+  EXPECT_EQ(result.GetDimension(1).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(1).Extent(), 3);
+  EXPECT_EQ(result.type(), (TypeCode{TypeCategory::Integer, 4}));
+  static std::int32_t eoshiftExpect1[6]{2, -1, -2, 3, -3, -3};
+  for (int j{0}; j < 6; ++j) {
+    EXPECT_EQ(
+        *result.ZeroBasedIndexedElement<std::int32_t>(j), eoshiftExpect1[j]);
+  }
+  result.Destroy();
+}
+
+TEST(Transformational, Pack) {
+  // ARRAY  1 3 5
+  //        2 4 6
+  auto array{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{2, 3}, std::vector<std::int32_t>{1, 2, 3, 4, 5, 6})};
+  array->GetDimension(0).SetLowerBound(2); // shouldn't matter
+  array->GetDimension(1).SetLowerBound(-1);
+  auto mask{MakeArray<TypeCategory::Logical, 1>(std::vector<int>{2, 3},
+      std::vector<std::uint8_t>{false, true, true, false, false, true})};
+  mask->GetDimension(0).SetLowerBound(0); // shouldn't matter
+  mask->GetDimension(1).SetLowerBound(2);
+  StaticDescriptor<1, true> statDesc;
+  Descriptor &result{statDesc.descriptor()};
+
+  RTNAME(Pack)(result, *array, *mask, nullptr, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), array->type());
+  EXPECT_EQ(result.rank(), 1);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 3);
+  static std::int32_t packExpect1[3]{2, 3, 6};
+  for (int j{0}; j < 3; ++j) {
+    EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int32_t>(j), packExpect1[j])
+        << " at " << j;
+  }
+  result.Destroy();
+
+  auto vector{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{5}, std::vector<std::int32_t>{-1, -2, -3, -4, -5})};
+  RTNAME(Pack)(result, *array, *mask, &*vector, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), array->type());
+  EXPECT_EQ(result.rank(), 1);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 5);
+  static std::int32_t packExpect2[5]{2, 3, 6, -4, -5};
+  for (int j{0}; j < 5; ++j) {
+    EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int32_t>(j), packExpect2[j])
+        << " at " << j;
+  }
+  result.Destroy();
+}
+
+TEST(Transformational, Spread) {
+  auto array{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{3}, std::vector<std::int32_t>{1, 2, 3})};
+  array->GetDimension(0).SetLowerBound(2); // shouldn't matter
+  StaticDescriptor<2, true> statDesc;
+  Descriptor &result{statDesc.descriptor()};
+
+  RTNAME(Spread)(result, *array, 1, 2, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), array->type());
+  EXPECT_EQ(result.rank(), 2);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 2);
+  EXPECT_EQ(result.GetDimension(1).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(1).Extent(), 3);
+  for (int j{0}; j < 6; ++j) {
+    EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int32_t>(j), 1 + j / 2);
+  }
+  result.Destroy();
+
+  RTNAME(Spread)(result, *array, 2, 2, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), array->type());
+  EXPECT_EQ(result.rank(), 2);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 3);
+  EXPECT_EQ(result.GetDimension(1).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(1).Extent(), 2);
+  for (int j{0}; j < 6; ++j) {
+    EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int32_t>(j), 1 + j % 3);
+  }
+  result.Destroy();
+}
+
+TEST(Transformational, Transpose) {
+  // ARRAY  1 3 5
+  //        2 4 6
+  auto array{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{2, 3}, std::vector<std::int32_t>{1, 2, 3, 4, 5, 6})};
+  array->GetDimension(0).SetLowerBound(2); // shouldn't matter
+  array->GetDimension(1).SetLowerBound(-6);
+  StaticDescriptor<2, true> statDesc;
+  Descriptor &result{statDesc.descriptor()};
+  RTNAME(Transpose)(result, *array, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), array->type());
+  EXPECT_EQ(result.rank(), 2);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 3);
+  EXPECT_EQ(result.GetDimension(1).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(1).Extent(), 2);
+  static std::int32_t expect[6]{1, 3, 5, 2, 4, 6};
+  for (int j{0}; j < 6; ++j) {
+    EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int32_t>(j), expect[j]);
+  }
+  result.Destroy();
+}
+
+TEST(Transformational, Unpack) {
+  auto vector{MakeArray<TypeCategory::Integer, 4>(
+      std::vector<int>{4}, std::vector<std::int32_t>{1, 2, 3, 4})};
+  vector->GetDimension(0).SetLowerBound(2); // shouldn't matter
+  auto mask{MakeArray<TypeCategory::Logical, 1>(std::vector<int>{2, 3},
+      std::vector<std::uint8_t>{false, true, true, false, false, true})};
+  mask->GetDimension(0).SetLowerBound(0); // shouldn't matter
+  mask->GetDimension(1).SetLowerBound(2);
+  auto field{MakeArray<TypeCategory::Integer, 4>(std::vector<int>{2, 3},
+      std::vector<std::int32_t>{-1, -2, -3, -4, -5, -6})};
+  field->GetDimension(0).SetLowerBound(-1); // shouldn't matter
+  StaticDescriptor<2, true> statDesc;
+  Descriptor &result{statDesc.descriptor()};
+  RTNAME(Unpack)(result, *vector, *mask, *field, __FILE__, __LINE__);
+  EXPECT_EQ(result.type(), vector->type());
+  EXPECT_EQ(result.rank(), 2);
+  EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(0).Extent(), 2);
+  EXPECT_EQ(result.GetDimension(1).LowerBound(), 1);
+  EXPECT_EQ(result.GetDimension(1).Extent(), 3);
+  static std::int32_t expect[6]{-1, 1, 2, -4, -5, 3};
+  for (int j{0}; j < 6; ++j) {
+    EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int32_t>(j), expect[j]);
+  }
+  result.Destroy();
+}