Adding New Experimental Op Files for Densify

Adding Densify operation .cpp and .h for converting sparse
tensor metadata into a dense operand. Adding
ANEURALNETWORKS_DENSIFY operation code. Creating
NeuralNetworksExperimentalFeatures.h for experimental
operation code. Modifying files to allow experimental
features.

Bug: 152069780
Test: mma
Change-Id: I18f46e6af904eca6848a3295def115374dd4caf1
diff --git a/common/Android.bp b/common/Android.bp
index b28f1fb..b02e500 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -36,6 +36,7 @@
         "operations/Comparisons.cpp",
         "operations/Concatenation.cpp",
         "operations/Conv2D.cpp",
+        "operations/Densify.cpp",
         "operations/DepthwiseConv2D.cpp",
         "operations/Dequantize.cpp",
         "operations/Elementwise.cpp",
@@ -154,8 +155,8 @@
     ],
 }
 
-cc_library_static {
-    name: "libneuralnetworks_common",
+cc_defaults {
+    name: "libneuralnetworks_common_defaults",
     defaults: [
         "neuralnetworks_defaults",
         "neuralnetworks_operations",
@@ -283,6 +284,17 @@
     ],
 }
 
+cc_library_static {
+    name: "libneuralnetworks_common",
+    defaults: ["libneuralnetworks_common_defaults"],
+}
+
+cc_library_static {
+    name: "libneuralnetworks_common_experimental",
+    defaults: ["libneuralnetworks_common_defaults"],
+    cflags: ["-DNN_EXPERIMENTAL_FEATURE"],
+}
+
 cc_defaults {
     name: "neuralnetworks_cl_defaults",
     host_supported: false,
diff --git a/common/OperationResolver.cpp b/common/OperationResolver.cpp
index e6792b2..5f5a9b2 100644
--- a/common/OperationResolver.cpp
+++ b/common/OperationResolver.cpp
@@ -97,6 +97,9 @@
 const OperationRegistration* register_TRANSPOSE_CONV_2D();
 const OperationRegistration* register_UNIDIRECTIONAL_SEQUENCE_LSTM();
 const OperationRegistration* register_UNIDIRECTIONAL_SEQUENCE_RNN();
+#ifdef NN_EXPERIMENTAL_FEATURE
+const OperationRegistration* register_DENSIFY();
+#endif  // NN_EXPERIMENTAL_FEATURE
 
 BuiltinOperationResolver::BuiltinOperationResolver() {
     registerOperation(register_ABS());
@@ -172,21 +175,38 @@
     registerOperation(register_TRANSPOSE_CONV_2D());
     registerOperation(register_UNIDIRECTIONAL_SEQUENCE_LSTM());
     registerOperation(register_UNIDIRECTIONAL_SEQUENCE_RNN());
+#ifdef NN_EXPERIMENTAL_FEATURE
+    registerOperation(register_DENSIFY());
+#endif  // NN_EXPERIMENTAL_FEATURE
 }
 
 const OperationRegistration* BuiltinOperationResolver::findOperation(
         OperationType operationType) const {
     auto index = static_cast<int32_t>(operationType);
-    if (index < 0 || index >= kNumberOfOperationTypes) {
-        return nullptr;
+    if (index >= 0 && index < kNumberOfOperationTypes) {
+        return mRegistrations[index];
     }
-    return mRegistrations[index];
+#ifdef NN_EXPERIMENTAL_FEATURE
+    if (index >= kStartOfExperimentalOperations &&
+        index < kStartOfExperimentalOperations + kNumberOfExperimentalOperationTypes) {
+        return mExperimentalRegistrations[index - kStartOfExperimentalOperations];
+    }
+#endif  // NN_EXPERIMENTAL_FEATURE
+    return nullptr;
 }
 
 void BuiltinOperationResolver::registerOperation(
         const OperationRegistration* operationRegistration) {
     CHECK(operationRegistration != nullptr);
     auto index = static_cast<int32_t>(operationRegistration->type);
+#ifdef NN_EXPERIMENTAL_FEATURE
+    if (index >= kStartOfExperimentalOperations) {
+        CHECK_LT(index, kStartOfExperimentalOperations + kNumberOfExperimentalOperationTypes);
+        CHECK(mExperimentalRegistrations[index - kStartOfExperimentalOperations] == nullptr);
+        mExperimentalRegistrations[index - kStartOfExperimentalOperations] = operationRegistration;
+        return;
+    }
+#endif  // NN_EXPERIMENTAL_FEATURE
     CHECK_LE(0, index);
     CHECK_LT(index, kNumberOfOperationTypes);
     CHECK(mRegistrations[index] == nullptr);
diff --git a/common/TypeUtils.cpp b/common/TypeUtils.cpp
index ce2cd51..2108c68 100644
--- a/common/TypeUtils.cpp
+++ b/common/TypeUtils.cpp
@@ -555,6 +555,10 @@
             return os << "RANK";
         case OperationType::OEM_OPERATION:
             return os << "OEM_OPERATION";
+#ifdef NN_EXPERIMENTAL_FEATURE
+        case OperationType::DENSIFY:
+            return os << "DENSIFY";
+#endif  // NN_EXPERIMENTAL_FEATURE
     }
     if (isExtension(operationType)) {
         return os << "Extension OperationType " << underlyingType(operationType);
@@ -894,6 +898,10 @@
             return os << "ANDROID_S";
         case Version::CURRENT_RUNTIME:
             return os << "CURRENT_RUNTIME";
+#ifdef NN_EXPERIMENTAL_FEATURE
+        case Version::EXPERIMENTAL:
+            return os << "EXPERIMENTAL";
+#endif  // NN_EXPERIMENTAL_FEATURE
     }
     return os << "Version{" << underlyingType(version) << "}";
 }
diff --git a/common/include/LegacyUtils.h b/common/include/LegacyUtils.h
index 97d9bff..5c705a5 100644
--- a/common/include/LegacyUtils.h
+++ b/common/include/LegacyUtils.h
@@ -42,6 +42,11 @@
 
 // The number of operation types (OperationCode) defined in NeuralNetworks.h.
 const int kNumberOfOperationTypes = 102;
+
+#ifdef NN_EXPERIMENTAL_FEATURE
+const int kNumberOfExperimentalOperationTypes = 1;
+#endif  // NN_EXPERIMENTAL_FEATURE
+
 static_assert(kNumberOfOperationTypes == BuiltinOperationResolver::kNumberOfOperationTypes);
 
 // The number of execution preferences defined in NeuralNetworks.h.
diff --git a/common/include/OperationResolver.h b/common/include/OperationResolver.h
index 04719b9..d88a244 100644
--- a/common/include/OperationResolver.h
+++ b/common/include/OperationResolver.h
@@ -93,12 +93,27 @@
     // The number of operation types (OperationCode) defined in NeuralNetworks.h.
     static constexpr int kNumberOfOperationTypes = 102;
 
+#ifdef NN_EXPERIMENTAL_FEATURE
+    // The number of experimental operation types (ANeuralNetworksExperimentalOperationCode) defined
+    // in NeuralNetworksExperimentalFeatures.h.
+    static constexpr int kNumberOfExperimentalOperationTypes = 1;
+
+    // The starting value of experimental operation types (ANeuralNetworksExperimentalOperationCode)
+    // defined in NeuralNetworksExperimentalFeatures.h.
+    static constexpr int kStartOfExperimentalOperations = 20000;
+#endif  // NN_EXPERIMENTAL_FEATURE
+
    private:
     BuiltinOperationResolver();
 
     void registerOperation(const OperationRegistration* operationRegistration);
 
     const OperationRegistration* mRegistrations[kNumberOfOperationTypes] = {};
+
+#ifdef NN_EXPERIMENTAL_FEATURE
+    const OperationRegistration* mExperimentalRegistrations[kNumberOfExperimentalOperationTypes] =
+            {};
+#endif  // NN_EXPERIMENTAL_FEATURE
 };
 
 // NN_REGISTER_OPERATION creates OperationRegistration for consumption by
diff --git a/common/include/nnapi/Types.h b/common/include/nnapi/Types.h
index f8124f4..e61fcce 100644
--- a/common/include/nnapi/Types.h
+++ b/common/include/nnapi/Types.h
@@ -979,7 +979,17 @@
 // Returns status, timingLaunched, timingFenced
 using ExecuteFencedInfoCallback = std::function<GeneralResult<std::pair<Timing, Timing>>()>;
 
-enum class Version { ANDROID_OC_MR1, ANDROID_P, ANDROID_Q, ANDROID_R, ANDROID_S, CURRENT_RUNTIME };
+enum class Version {
+    ANDROID_OC_MR1,
+    ANDROID_P,
+    ANDROID_Q,
+    ANDROID_R,
+    ANDROID_S,
+    CURRENT_RUNTIME,
+#ifdef NN_EXPERIMENTAL_FEATURE
+    EXPERIMENTAL,
+#endif  // NN_EXPERIMENTAL_FEATURE
+};
 
 // Describes the memory preference of an operand.
 struct MemoryPreference {
diff --git a/common/operations/Densify.cpp b/common/operations/Densify.cpp
new file mode 100644
index 0000000..ce831ed
--- /dev/null
+++ b/common/operations/Densify.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef NN_EXPERIMENTAL_FEATURE
+
+#include "Densify.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <numeric>
+#include <vector>
+
+#include "OperationResolver.h"
+#include "OperationsUtils.h"
+#include "Tracing.h"
+#include "nnapi/OperandTypes.h"
+#include "nnapi/TypeUtils.h"
+#include "nnapi/Validation.h"
+
+#define LOG_TAG "Operations"
+
+namespace android {
+namespace nn {
+namespace densify_op {
+
+constexpr uint32_t kMinNumInputs = 5;
+constexpr uint32_t kInputTensor = 0;
+constexpr uint32_t kInputTravOrder = 1;
+constexpr uint32_t kInputBlockMap = 2;
+constexpr uint32_t kInputDimFormat = 3;
+constexpr uint32_t kInputDimensions = 4;
+constexpr uint32_t kInputArrSeg = 5;
+constexpr uint32_t kInputArrIdx = 6;
+constexpr uint32_t kNumOutputs = 1;
+constexpr uint32_t kOutputTensor = 0;
+constexpr int32_t DENSE = 0;
+constexpr int32_t SPARSE_CSR = 1;
+
+uint64_t getFlattenedIndex(const std::vector<int32_t>& indices, const std::vector<uint32_t>& shape,
+                           const int origRank) {
+    uint64_t index = 0;
+    int subElems = 1;
+    // origRank = size of destDims
+    for (int i = origRank - 1; i >= 0; i--) {
+        index += uint64_t(indices[i] * subElems);
+        subElems *= shape[i];
+    }
+    return index;
+}
+
+template <typename T>
+void populate(const T* srcData, std::vector<int32_t>* indices, int32_t level, int32_t prevIdx,
+              T* destData, const std::vector<uint32_t>& destDims,
+              const std::vector<int32_t>& dimFormat, const int32_t* traversalOrder,
+              const std::vector<int32_t>& blockSize, const int32_t* blockMap,
+              const std::vector<std::vector<int32_t>>& dimMetadata, const int origRank) {
+    if (level == (*indices).size()) {  // level == size of traversal order
+        std::vector<int> origIdx(origRank);
+        int i = 0;
+        // Calculating origIdx using dense tensor dimensions
+        for (; i < origIdx.size(); i++) {
+            int origDim = traversalOrder[i];
+            origIdx[origDim] = (*indices)[i];
+        }
+        // Modifying origIdx using block dimensions
+        for (; i < (*indices).size(); i++) {
+            const int blockIdx = traversalOrder[i] - origRank;
+            const int origDim = blockMap[blockIdx];
+            origIdx[origDim] = origIdx[origDim] * blockSize[blockIdx] + (*indices)[i];
+        }
+        // Writing srcData to destData
+        destData[getFlattenedIndex(origIdx, destDims, origRank)] = srcData[prevIdx];
+        return;
+    }
+    const int metadataIdx = 2 * level;
+    if (dimFormat[level] == DENSE) {  // DENSE dimension format
+        const int shapeOfLevel = dimMetadata[metadataIdx].front();
+        for (int i = 0; i < shapeOfLevel; i++) {
+            (*indices)[level] = i;
+            populate(srcData, indices, level + 1, prevIdx * shapeOfLevel + i, destData, destDims,
+                     dimFormat, traversalOrder, blockSize, blockMap, dimMetadata, origRank);
+        }
+    } else {  // SPARSE_CSR dimension format
+        const auto& arraySegments = dimMetadata[metadataIdx];
+        const auto& arrayIndices = dimMetadata[metadataIdx + 1];
+        for (int i = arraySegments[prevIdx]; i < arraySegments[prevIdx + 1]; i++) {
+            (*indices)[level] = arrayIndices[i];
+            populate(srcData, indices, level + 1, i, destData, destDims, dimFormat, traversalOrder,
+                     blockSize, blockMap, dimMetadata, origRank);
+        }
+    }
+}
+
+template <typename T>
+std::vector<T> arrToVector(const T* arr, uint32_t size) {
+    return arr == nullptr ? std::vector<T>() : std::vector<T>(arr, arr + size);
+}
+
+template <typename T>
+inline bool densify(IOperationExecutionContext* context) {
+    // Getting all inputs
+    std::vector<Shape> inputShapes;
+    const uint32_t inputCount = context->getNumInputs();
+    inputShapes.reserve(inputCount);
+    const T* srcData = context->getInputBuffer<T>(kInputTensor);
+    inputShapes.push_back(context->getInputShape(kInputTensor));
+    const int32_t* traversalOrder = context->getInputBuffer<int32_t>(kInputTravOrder);
+    inputShapes.push_back(context->getInputShape(kInputTravOrder));
+    const int32_t* blockMap = context->getInputBuffer<int32_t>(kInputBlockMap);
+    inputShapes.push_back(context->getInputShape(kInputBlockMap));
+    const int32_t* dimFormatPtr = context->getInputBuffer<int32_t>(kInputDimFormat);
+    inputShapes.push_back(context->getInputShape(kInputDimFormat));
+    const int32_t* dimensionsPtr = context->getInputBuffer<int32_t>(kInputDimensions);
+    inputShapes.push_back(context->getInputShape(kInputDimensions));
+
+    std::vector<const int32_t*> dimMetadataPtrs;
+    for (uint32_t i = kInputArrSeg; i < inputCount; i++) {
+        inputShapes.push_back(context->getInputShape(i));
+        const int32_t* metadata = context->getInputBuffer<int32_t>(i);
+        dimMetadataPtrs.push_back(metadata);
+    }
+    Shape destShape = context->getOutputShape(kOutputTensor);
+
+    // Organizing dimFormat, dimensions, dimMetadata into vectors
+    std::vector<int32_t> dimFormat(
+            inputShapes[kInputDimFormat].dimensions.front());  // size of dimFormatPtr
+    std::vector<int32_t> dimensions(dimFormat.size());
+    std::vector<std::vector<int32_t>> dimMetadata(2 * dimFormat.size());
+    for (size_t i = 0; i < dimFormat.size(); i++) {
+        dimFormat[i] = dimFormatPtr[i];
+        dimensions[i] = dimensionsPtr[i];
+        if (dimFormat[i] == 0) {
+            dimMetadata[i * 2] = {dimensions[i]};
+        } else {
+            dimMetadata[i * 2] =  // array segments
+                    arrToVector(dimMetadataPtrs[i * 2],
+                                inputShapes[i * 2 + kInputArrSeg].dimensions.front());
+            dimMetadata[i * 2 + 1] =  // array indices
+                    arrToVector(dimMetadataPtrs[i * 2 + 1],
+                                inputShapes[i * 2 + kInputArrIdx].dimensions.front());
+        }
+    }
+
+    // Creating blockSize vector
+    const int origRank = destShape.dimensions.size();
+    std::vector<int32_t> blockSize(
+            inputShapes[kInputBlockMap].dimensions.front());  // size of block map
+    for (int i = 0; i < inputShapes[kInputBlockMap].dimensions.front(); i++) {
+        const int32_t origDim = traversalOrder[origRank + i];
+        blockSize[i] = dimensions[origDim];
+    }
+
+    // Calculating the number of output entries
+    const size_t denseTotal =
+            std::accumulate(destShape.dimensions.begin(), destShape.dimensions.end(),
+                            static_cast<size_t>(1), std::multiplies<>{});
+    T zeroPoint = T();
+    if (const OperandType type = inputShapes.front().type;
+        type == OperandType::TENSOR_QUANT8_ASYMM ||
+        type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED ||
+        type == OperandType::TENSOR_QUANT16_ASYMM) {
+        zeroPoint = static_cast<T>(inputShapes.front().offset);
+    }
+
+    T* destData = context->getOutputBuffer<T>(kOutputTensor);
+    for (int32_t i = 0; i < denseTotal; i++) {
+        destData[i] = zeroPoint;
+    }
+
+    std::vector<int32_t> indices(
+            inputShapes[kInputTravOrder].dimensions.front());  // size of traversal order
+    populate(srcData, &indices, 0, 0, destData, destShape.dimensions, dimFormat, traversalOrder,
+             blockSize, blockMap, dimMetadata, origRank);
+    return true;
+}
+
+Result<Version> validate(const IOperationValidationContext* context) {
+    // Checking number of inputs and outputs
+    const uint32_t inputCount = context->getNumInputs();
+    NN_RET_CHECK_GE(inputCount, kMinNumInputs);
+    NN_RET_CHECK_EQ(inputCount,
+                    kMinNumInputs + context->getInputShape(kInputTravOrder).dimensions.front() * 2);
+    NN_RET_CHECK_EQ(context->getNumOutputs(), kNumOutputs);
+    NN_RET_CHECK_EQ(context->getInputShape(kInputTensor).dimensions.size(), 1);
+    for (uint32_t i = 1; i < inputCount; i++) {
+        NN_RET_CHECK_EQ(context->getInputShape(i).dimensions.size(), 1);
+        NN_RET_CHECK_EQ(context->getInputType(i), OperandType::TENSOR_INT32);
+    }
+    return Version::EXPERIMENTAL;
+}
+
+bool prepare(IOperationExecutionContext* context) {
+    // Setting OutputShape
+    Shape destShape = context->getInputShape(kInputTensor);
+
+    const int32_t* traversalOrder = context->getInputBuffer<int32_t>(kInputTravOrder);
+    const int32_t* blockMap = context->getInputBuffer<int32_t>(kInputBlockMap);
+    const int32_t* dimensions = context->getInputBuffer<int32_t>(kInputDimensions);
+    Shape dimensionsShape = context->getInputShape(kInputDimensions);
+    Shape blockMapShape = context->getInputShape(kInputBlockMap);
+    const uint32_t origRank = dimensionsShape.dimensions.front() - blockMapShape.dimensions.front();
+    std::vector<uint32_t> destDims(origRank);
+
+    int i = 0;
+    for (; i < destDims.size(); i++) {
+        const int32_t origDim = traversalOrder[i];
+        destDims[origDim] = dimensions[i];
+    }
+    for (; i < dimensionsShape.dimensions.front(); i++) {
+        const int32_t traversalIdx = traversalOrder[i] - origRank;
+        const int32_t origDim = blockMap[traversalIdx];
+        destDims[origDim] *= dimensions[i];
+    }
+    destShape.dimensions = destDims;
+    return context->setOutputShape(kOutputTensor, destShape);
+}
+
+bool execute(IOperationExecutionContext* context) {
+    switch (context->getInputType(kInputTensor)) {
+        case OperandType::TENSOR_BOOL8:
+            return densify<bool8>(context);
+        case OperandType::TENSOR_FLOAT32:
+            return densify<float>(context);
+        case OperandType::TENSOR_FLOAT16:
+            return densify<_Float16>(context);
+        case OperandType::TENSOR_INT32:
+            return densify<int32_t>(context);
+        case OperandType::TENSOR_QUANT8_ASYMM:
+            return densify<uint8_t>(context);
+        case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+        case OperandType::TENSOR_QUANT8_SYMM:
+            return densify<int8_t>(context);
+        case OperandType::TENSOR_QUANT16_SYMM:
+            return densify<int16_t>(context);
+        case OperandType::TENSOR_QUANT16_ASYMM:
+            return densify<uint16_t>(context);
+        default:
+            return false;
+    }
+}
+
+}  // namespace densify_op
+
+NN_REGISTER_OPERATION(DENSIFY, "DENSIFY", densify_op::validate, densify_op::prepare,
+                      densify_op::execute, .allowOmittedOperand = true);
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // NN_EXPERIMENTAL_FEATURE
\ No newline at end of file
diff --git a/common/operations/Densify.h b/common/operations/Densify.h
new file mode 100644
index 0000000..8ffed34
--- /dev/null
+++ b/common/operations/Densify.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FRAMEWORKS_ML_NN_COMMON_OPERATIONS_DENSIFY_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_OPERATIONS_DENSIFY_H
+
+#include <vector>
+
+#include "LegacyUtils.h"
+#include "OperationResolver.h"
+#include "OperationsUtils.h"
+
+namespace android {
+namespace nn {
+namespace densify_op {
+
+/**
+ * getFlattenedIndex:
+ * Gets the index of destData where indices points to. Uses shape and origRank
+ * for calculations.
+ */
+uint64_t getFlattenedIndex(const std::vector<int32_t>& indices, const std::vector<uint32_t>& shape,
+                           const int origRank);
+
+/**
+ * populate (Recursive Function):
+ * Used to populate the destData with elements from srcData one value at a time.
+ * Inputs:
+ * * srcData = input data of non-zero values.
+ * * indices = used to determine the index in destData where we write srcData to. Uses block
+ *   dimension.
+ * * level = used to keep track of recursion level. Each recursive instance exits when level == size
+ *   of traversal order.
+ * * prevIdx = used to keep placement in array segments and srcData.
+ * * destData = dense output data. Input being written to.
+ * * denseShape = shape of the output tensor. Used to calculate the flattened idx.
+ * * dimFormat = dimension format for each entry in traversal order. The format is either DENSE
+ *   (dimFormat[i] == 0) or SPARSE_CSR (dimFormat[i] == 1). Format is significant to determine how
+ *   recursive iterations will occur and what metadata is stored in dimMetadata.
+ * * traversalOrder = contains n+k elements. The first n elements are a permutation of the dense
+ *   tensor shape. The last k elements are a permutation of the block dimensions. Used to determine
+ *   order of traversal paths.
+ * * blockSize = dense size of blocks. The last k elements of dimensions.
+ * * blockMap = Used to determine how the block dimension maps to the original tensor dimension.
+ * * dimMetadata = metadata varies depending on dimFormat values. If format is DENSE,
+ *   dimMetadata[i*2][0] is the total number of elements in the dense tensor on the ith traversal
+ *   path, and recursive iterations are through a standard for loop from 0 to dimMetadata[i*2][0].
+ *   If format is SPARSE_CSR, dimMetadata[i*2] is a vector of array segments and
+ *   dimMetadata[i*2+1] is a vector of array indices. The next recursive iterations will be
+ *   looping through the array segments vector (since array segments are the same as row pointers in
+ *   CSR format, the ith entry should never be greater than the ith+1 entry) and modifying the input
+ *   indices with elements from the array indices vector.
+ * * origRank = the size of denseShape. Used for calculating flattened index of indices.
+ */
+template <typename T>
+void populate(const T* srcData, std::vector<int32_t>* indices, int32_t level, int32_t prevIdx,
+              T* destData, const std::vector<uint32_t>& denseShape,
+              const std::vector<int32_t>& dimFormat, const int32_t* traversalOrder,
+              const std::vector<int32_t>& blockSize, const int32_t* blockMap,
+              const std::vector<std::vector<int32_t>>& dimMetadata, const int origRank);
+
+/**
+ * arrToVector:
+ * Converts a T array into an T vector.
+ */
+template <typename T>
+std::vector<T> arrToVector(const T* arr, uint32_t size);
+
+/**
+ * densify:
+ * Core of execution function. Prepares inputs for Populate function.
+ */
+template <typename T>
+inline bool densify(IOperationExecutionContext* context);
+
+Result<Version> validate(const IOperationValidationContext* context);
+
+bool prepare(IOperationExecutionContext* context);
+
+bool execute(IOperationExecutionContext* context);
+
+}  // namespace densify_op
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_FRAMEWORKS_ML_NN_COMMON_OPERATIONS_DENSIFY_H