IVGCVSW-6989 "Merged experimental/armnn_shim_sl"

* Updated Serializer CMakeLists.txt to build armnnSerializerObj
* Added constant tensors as input support to SL

Signed-off-by: Sadik Armagan <sadik.armagan@arm.com>
Change-Id: I22f6cf50147d99a01f7fe70d7446b114a4c57af3
diff --git a/shim/sl/canonical/ConversionUtils.cpp b/shim/sl/canonical/ConversionUtils.cpp
new file mode 100644
index 0000000..fbc5e28
--- /dev/null
+++ b/shim/sl/canonical/ConversionUtils.cpp
@@ -0,0 +1,1006 @@
+//
+// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "ConversionUtils.hpp"
+#include <armnnUtils/Permute.hpp>
+
+///
+/// Helper classes
+///
+
+namespace armnn_driver
+{
+
+LayerInputHandle::LayerInputHandle()
+    : m_OutputSlot(nullptr)
+    , m_Valid(false)
+{}
+
+LayerInputHandle::LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo)
+    : m_OutputSlot(outputSlot)
+    , m_Valid(valid)
+    , m_TensorInfo(tensorInfo)
+{}
+
+bool LayerInputHandle::IsValid() const
+{
+    return m_Valid;
+}
+
+void LayerInputHandle::Connect(armnn::IInputSlot& inputSlot)
+{
+    ARMNN_ASSERT(IsValid());
+    if (m_OutputSlot)
+    {
+        m_OutputSlot->Connect(inputSlot);
+    }
+}
+
+void LayerInputHandle::Disconnect(armnn::IInputSlot& inputSlot)
+{
+    ARMNN_ASSERT(IsValid());
+    if (m_OutputSlot)
+    {
+        m_OutputSlot->Disconnect(inputSlot);
+    }
+}
+
+const armnn::TensorInfo& LayerInputHandle::GetTensorInfo() const
+{
+    return m_TensorInfo;
+}
+
+void LayerInputHandle::SanitizeQuantizationScale(LayerInputHandle& weight, LayerInputHandle& input)
+{
+    if (m_OutputSlot)
+    {
+        armnn::TensorInfo weightInfo = weight.GetTensorInfo();
+        armnn::TensorInfo inputInfo = input.GetTensorInfo();
+        armnn::TensorInfo biasInfo = GetTensorInfo();
+
+        SanitizeBiasQuantizationScale(biasInfo, weightInfo, inputInfo);
+
+        m_TensorInfo = biasInfo;
+        m_OutputSlot->SetTensorInfo(biasInfo);
+    }
+}
+
+ConstTensorPin::ConstTensorPin(bool optional)
+    : m_Optional(optional)
+{}
+
+ConstTensorPin::ConstTensorPin(armnn::TensorInfo& tensorInfo,
+                               const void* valueStart,
+                               uint32_t numBytes,
+                               const armnn::PermutationVector& mappings)
+    : m_Optional(false)
+{
+    armnn::IgnoreUnused(numBytes);
+    if (tensorInfo.GetNumBytes() != numBytes)
+    {
+        VLOG(DRIVER) << "The size of ConstTensor does not match its TensorInfo.";
+    }
+
+    const bool needsSwizzling = (mappings.GetSize() > 0);
+    if (needsSwizzling)
+    {
+        m_SwizzledTensorData.resize(tensorInfo.GetNumBytes());
+        SwizzleAndroidNn4dTensorToArmNn(tensorInfo, valueStart, m_SwizzledTensorData.data(), mappings);
+
+        m_ConstTensor = armnn::ConstTensor(tensorInfo, m_SwizzledTensorData.data());
+    }
+    else
+    {
+        m_ConstTensor = armnn::ConstTensor(tensorInfo, valueStart);
+    }
+}
+
+bool ConstTensorPin::IsValid() const
+{
+    return m_ConstTensor.GetMemoryArea() != nullptr;
+}
+
+bool ConstTensorPin::IsOptional() const
+{
+    return m_Optional;
+}
+
+const armnn::ConstTensor& ConstTensorPin::GetConstTensor() const
+{
+    return m_ConstTensor;
+}
+
+const armnn::ConstTensor* ConstTensorPin::GetConstTensorPtr() const
+{
+    if (IsValid() && m_ConstTensor.GetNumElements() > 0)
+    {
+        return &m_ConstTensor;
+    }
+    // tensor is either invalid, or has no elements (indicating an optional tensor that was not provided)
+    return nullptr;
+}
+
+///
+/// Utility functions
+///
+
+bool IsWeightsValid(const Operation& operation,
+                    uint32_t inputIndex,
+                    const Model& model)
+{
+    const Operand* operand = GetInputOperand(operation, inputIndex, model);
+    if (!operand)
+    {
+        Fail("%s: failed to get input operand %i", __func__, inputIndex);
+        return false;
+    }
+
+    if (operand->lifetime    != OperandLifeTime::CONSTANT_COPY
+        && operand->lifetime != OperandLifeTime::CONSTANT_REFERENCE
+        && operand->lifetime != OperandLifeTime::NO_VALUE)
+    {
+        return false;
+    }
+    return true;
+}
+
+ConstTensorPin ConvertOperandToConstTensorPin(const Operand& operand,
+                                              const Model& model,
+                                              const ConversionData& data,
+                                              const armnn::PermutationVector& dimensionMappings,
+                                              const armnn::TensorShape* overrideTensorShape,
+                                              bool optional)
+{
+    if (!IsOperandTypeSupportedForTensors(operand.type))
+    {
+        VLOG(DRIVER) << __func__ << ": unsupported operand type for tensor" << operand.type;
+        return ConstTensorPin();
+    }
+
+    if (!optional && !IsOperandConstant(operand))
+    {
+        VLOG(DRIVER) << __func__ << ": lifetime for input tensor: r" << operand.lifetime;
+        return ConstTensorPin();
+    }
+
+    const void* const valueStart = GetOperandValueReadOnlyAddress(operand, model, data, optional);
+    if (!valueStart)
+    {
+        if (optional)
+        {
+            // optional tensor with no values is not really an error; return it as invalid, but marked as optional
+            return ConstTensorPin(true);
+        }
+        // mandatory tensor with no values
+        Fail("%s: failed to get operand address", __func__);
+        return ConstTensorPin();
+    }
+
+    armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand);
+
+    // Make sure isConstant flag is set.
+    tensorInfo.SetConstant();
+
+    if (overrideTensorShape != nullptr)
+    {
+        tensorInfo.SetShape(*overrideTensorShape);
+    }
+    return ConstTensorPin(tensorInfo, valueStart, operand.location.length, dimensionMappings);
+}
+
+LayerInputHandle ConvertToLayerInputHandle(const Operation& operation,
+                                           uint32_t inputIndex,
+                                           const Model& model,
+                                           ConversionData& data,
+                                           const armnn::PermutationVector& dimensionMappings)
+{
+
+    const Operand* operand = GetInputOperand(operation, inputIndex, model);
+    if (!operand)
+    {
+        Fail("%s: failed to get input operand %i", __func__, inputIndex);
+        return LayerInputHandle();
+    }
+
+    if (!IsOperandTypeSupportedForTensors(operand->type))
+    {
+        VLOG(DRIVER) << __func__ << ": unsupported operand type for tensor: " << operand->type;
+        return LayerInputHandle();
+    }
+
+    try
+    {
+        armnn::TensorInfo operandTensorInfo = GetTensorInfoForOperand(*operand);
+
+        if (IsDynamicTensor(operandTensorInfo))
+        {
+            data.m_DynamicInputsEncountered = true;
+
+            const uint32_t operandIndex = operation.inputs[inputIndex];
+
+            // Check if the dynamic input tensors have been inferred by one of the previous layers
+            // If not we can't support them
+            if (data.m_OutputSlotForOperand.size() >= operandIndex && data.m_OutputSlotForOperand[operandIndex])
+            {
+                operandTensorInfo = data.m_OutputSlotForOperand[operandIndex]->GetTensorInfo();
+            }
+            else
+            {
+                Fail("%s: Type 2 dynamic input tensors are not supported", __func__);
+                return LayerInputHandle();
+            }
+        }
+
+        switch (operand->lifetime)
+        {
+            case OperandLifeTime::SUBGRAPH_INPUT:
+            {
+                // NOTE: We must check whether we can support the input tensor on at least one
+                // of the provided backends; otherwise we cannot convert the operation
+                bool isInputSupported = false;
+                FORWARD_LAYER_SUPPORT_FUNC(__func__,
+                                           IsInputSupported,
+                                           data.m_Backends,
+                                           isInputSupported,
+                                           operandTensorInfo);
+
+                if (!isInputSupported)
+                {
+                    Fail("%s: unsupported input tensor", __func__);
+                    return LayerInputHandle();
+                }
+
+                [[clang::fallthrough]]; // intentional fallthrough
+            }
+            case OperandLifeTime::TEMPORARY_VARIABLE: // intentional fallthrough
+            case OperandLifeTime::SUBGRAPH_OUTPUT:
+            {
+                // The tensor is either an operand internal to the model, or a model input.
+                // It can be associated with an ArmNN output slot for an existing layer.
+
+                // m_OutputSlotForOperand[...] can be nullptr if the previous layer could not be converted
+                const uint32_t operandIndex = operation.inputs[inputIndex];
+                return LayerInputHandle(true, data.m_OutputSlotForOperand[operandIndex], operandTensorInfo);
+            }
+            case OperandLifeTime::CONSTANT_COPY: // intentional fallthrough
+            case OperandLifeTime::POINTER:
+            case OperandLifeTime::CONSTANT_REFERENCE:
+            {
+                // The tensor has an already known constant value, and can be converted into an ArmNN Constant layer.
+                ConstTensorPin tensorPin = ConvertOperandToConstTensorPin(*operand, model, data, dimensionMappings);
+                if (tensorPin.IsValid())
+                {
+                    bool isSupported = false;
+                    FORWARD_LAYER_SUPPORT_FUNC(__func__,
+                                               IsConstantSupported,
+                                               data.m_Backends,
+                                               isSupported,
+                                               tensorPin.GetConstTensor().GetInfo());
+                    if (!isSupported)
+                    {
+                        return LayerInputHandle();
+                    }
+
+                    armnn::IConnectableLayer* constantLayer =
+                        data.m_Network->AddConstantLayer(tensorPin.GetConstTensor());
+                    armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
+                    outputSlot.SetTensorInfo(tensorPin.GetConstTensor().GetInfo());
+
+                    return LayerInputHandle(true, &outputSlot, operandTensorInfo);
+                }
+                else
+                {
+                    Fail("%s: invalid operand tensor", __func__);
+                    return LayerInputHandle();
+                }
+                break;
+            }
+            default:
+            {
+                VLOG(DRIVER) << __func__ << ": unsupported lifetime for input tensor: " << operand->lifetime;
+                return LayerInputHandle();
+            }
+        }
+    }
+    catch (UnsupportedOperand<OperandType>& e)
+    {
+        VLOG(DRIVER) << __func__ << ": Operand type: " << e.m_type << " not supported in ArmnnDriver";
+        return LayerInputHandle();
+    }
+}
+
+bool ConvertPaddings(const Operation& operation,
+                     const Model& model,
+                     ConversionData& data,
+                     unsigned int rank,
+                     armnn::PadDescriptor& padDescriptor)
+{
+    const Operand* paddingsOperand = GetInputOperand(operation, 1, model);
+    if (!paddingsOperand)
+    {
+        return Fail("%s: Could not read paddings operand", __func__);
+    }
+
+    armnn::TensorShape paddingsOperandShape = GetTensorShapeForOperand(*paddingsOperand);
+    if (paddingsOperandShape.GetNumDimensions() != 2 || paddingsOperandShape.GetNumElements() != rank * 2)
+    {
+        return Fail("%s: Operation has invalid paddings operand: expected shape [%d, 2]",  __func__, rank);
+    }
+
+    std::vector<int32_t> paddings;
+    if (!GetTensorInt32Values(*paddingsOperand, paddings, model, data))
+    {
+        return Fail("%s: Operation has invalid or unsupported paddings operand", __func__);
+    }
+
+    // add padding for each dimension of input tensor.
+    for (unsigned int i = 0; i < paddings.size() - 1; i += 2)
+    {
+        int paddingBeforeInput = paddings[i];
+        int paddingAfterInput  = paddings[i + 1];
+
+        if (paddingBeforeInput < 0 || paddingAfterInput < 0)
+        {
+            return Fail("%s: Operation has invalid paddings operand, invalid padding values.",  __func__);
+        }
+
+        padDescriptor.m_PadList.emplace_back((unsigned int) paddingBeforeInput, (unsigned int) paddingAfterInput);
+    }
+
+    return true;
+}
+
+
+bool ConvertPooling2d(const Operation& operation,
+                      const char* operationName,
+                      armnn::PoolingAlgorithm poolType,
+                      const Model& model,
+                      ConversionData& data)
+{
+
+    VLOG(DRIVER) << "Converter::ConvertL2Pool2d()";
+
+    LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
+    if (!input.IsValid())
+    {
+        return Fail("%s: Operation Could not read input 0", operationName);
+    }
+
+    const Operand* output = GetOutputOperand(operation, 0, model);
+    if (!output)
+    {
+        return Fail("%s: Could not read output 0", __func__);
+    }
+
+    const armnn::TensorInfo& inputInfo  = input.GetTensorInfo();
+    const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
+
+    armnn::Pooling2dDescriptor desc;
+    desc.m_PoolType = poolType;
+    desc.m_OutputShapeRounding = armnn::OutputShapeRounding::Floor;
+    desc.m_DataLayout = armnn::DataLayout::NHWC;
+
+    ActivationFn activation;
+
+    auto inputSize = operation.inputs.size();
+
+    if (inputSize >= 10)
+    {
+        // one input, 9 parameters (padding l r t b, stridex, stridey, width, height, activation type)
+        if (!GetInputScalar(operation, 1, OperandType::INT32, desc.m_PadLeft, model, data) ||
+            !GetInputScalar(operation, 2, OperandType::INT32, desc.m_PadRight, model, data) ||
+            !GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadTop, model, data) ||
+            !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadBottom, model, data) ||
+            !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideX, model, data) ||
+            !GetInputScalar(operation, 6, OperandType::INT32, desc.m_StrideY, model, data) ||
+            !GetInputScalar(operation, 7, OperandType::INT32, desc.m_PoolWidth, model, data) ||
+            !GetInputScalar(operation, 8, OperandType::INT32, desc.m_PoolHeight, model, data) ||
+            !GetInputActivationFunction(operation, 9, activation, model, data))
+        {
+            return Fail("%s: Operation has invalid inputs", operationName);
+        }
+
+        if (Is12OrLaterOperand(*output))
+        {
+            desc.m_DataLayout = OptionalDataLayout(operation, 10, model, data);
+        }
+    }
+    else
+    {
+        // one input, 6 parameters (padding, stridex, stridey, width, height, activation type)
+        ::android::nn::PaddingScheme scheme;
+        if (!GetInputPaddingScheme(operation, 1, scheme, model, data) ||
+            !GetInputScalar(operation, 2, OperandType::INT32, desc.m_StrideX, model, data) ||
+            !GetInputScalar(operation, 3, OperandType::INT32, desc.m_StrideY, model, data) ||
+            !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PoolWidth, model, data) ||
+            !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PoolHeight, model, data) ||
+            !GetInputActivationFunction(operation, 6, activation, model, data))
+        {
+            return Fail("%s: Operation has invalid inputs", operationName);
+        }
+
+        if (Is12OrLaterOperand(*output))
+        {
+            desc.m_DataLayout = OptionalDataLayout(operation, 7, model, data);
+        }
+
+        const armnnUtils::DataLayoutIndexed dataLayout(desc.m_DataLayout);
+        const unsigned int inputWidth  = inputInfo.GetShape()[dataLayout.GetWidthIndex()];
+        const unsigned int inputHeight = inputInfo.GetShape()[dataLayout.GetHeightIndex()];
+
+        CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, scheme);
+        CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, scheme);
+    }
+
+    bool isSupported = false;
+
+    auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
+    {
+        FORWARD_LAYER_SUPPORT_FUNC(__func__,
+                                   IsPooling2dSupported,
+                                   data.m_Backends,
+                                   isSupported,
+                                   inputInfo,
+                                   outputInfo,
+                                   desc);
+
+    };
+
+    if(IsDynamicTensor(outputInfo))
+    {
+        isSupported = AreDynamicTensorsSupported();
+    }
+    else
+    {
+        validateFunc(outputInfo, isSupported);
+    }
+
+    if (!isSupported)
+    {
+        return false;
+    }
+
+    armnn::IConnectableLayer* pooling2dLayer = data.m_Network->AddPooling2dLayer(desc);
+    if (!pooling2dLayer)
+    {
+        return Fail("%s: AddPooling2dLayer failed", __func__);
+    }
+
+    input.Connect(pooling2dLayer->GetInputSlot(0));
+
+    if (!isSupported)
+    {
+        return false;
+    }
+
+    return SetupAndTrackLayerOutputSlot(operation, 0, *pooling2dLayer, model,
+                                        data, nullptr, validateFunc, activation);
+}
+
+bool ConvertReduce(const Operation& operation,
+                   const Model& model,
+                   ConversionData& data,
+                   armnn::ReduceOperation reduceOperation)
+{
+    armnn::ReduceDescriptor descriptor;
+    descriptor.m_ReduceOperation = reduceOperation;
+
+    LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
+    if (!input.IsValid())
+    {
+        return Fail("%s: Operation has invalid inputs", __func__);
+    }
+    const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
+
+    const Operand* output = GetOutputOperand(operation, 0, model);
+    if (!output)
+    {
+        return Fail("%s: Could not read output 0", __func__);
+    }
+    const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
+
+    const Operand* axisOperand = GetInputOperand(operation, 1, model);
+    if (!axisOperand)
+    {
+        return Fail("%s: Could not read input 1", __func__);
+    }
+    std::vector<int32_t> axis;
+    if (!GetTensorInt32Values(*axisOperand, axis, model, data))
+    {
+        return Fail("%s: Input 1 has invalid values", __func__);
+    }
+
+    // Convert the axis to unsigned int and remove duplicates.
+    unsigned int rank = inputInfo.GetNumDimensions();
+    std::set<unsigned int> uniqueAxis;
+    std::transform(axis.begin(), axis.end(),
+                   std::inserter(uniqueAxis, uniqueAxis.begin()),
+                   [rank](int i) -> unsigned int { return (i + rank) % rank; });
+    descriptor.m_vAxis.assign(uniqueAxis.begin(), uniqueAxis.end());
+
+    // Get the "keep dims" flag.
+    if (!GetInputScalar(operation, 2, OperandType::BOOL, descriptor.m_KeepDims, model, data))
+    {
+        return Fail("%s: Could not read input 2", __func__);
+    }
+
+    bool isSupported = false;
+    auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
+    {
+        FORWARD_LAYER_SUPPORT_FUNC(__func__,
+                                   IsReduceSupported,
+                                   data.m_Backends,
+                                   isSupported,
+                                   inputInfo,
+                                   outputInfo,
+                                   descriptor);
+    };
+
+    if(!IsDynamicTensor(outputInfo))
+    {
+        validateFunc(outputInfo, isSupported);
+    }
+    else
+    {
+        isSupported = AreDynamicTensorsSupported();
+    }
+
+    if (!isSupported)
+    {
+        return false;
+    }
+
+    armnn::IConnectableLayer* const layer = data.m_Network->AddReduceLayer(descriptor);
+    assert(layer != nullptr);
+    input.Connect(layer->GetInputSlot(0));
+
+    return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
+}
+
+
+bool ConvertToActivation(const Operation& operation,
+                         const char* operationName,
+                         const armnn::ActivationDescriptor& activationDesc,
+                         const Model& model,
+                         ConversionData& data)
+{
+    LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
+    if (!input.IsValid())
+    {
+        return Fail("%s: Input 0 is invalid", operationName);
+    }
+
+    const Operand* outputOperand = GetOutputOperand(operation, 0, model);
+    if (!outputOperand)
+    {
+        return false;
+    }
+
+    const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
+
+    bool isSupported = false;
+
+    auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported)
+    {
+        FORWARD_LAYER_SUPPORT_FUNC(__func__,
+                                   IsActivationSupported,
+                                   data.m_Backends,
+                                   isSupported,
+                                   input.GetTensorInfo(),
+                                   outInfo,
+                                   activationDesc);
+    };
+
+    if(IsDynamicTensor(outInfo))
+    {
+        isSupported = AreDynamicTensorsSupported();
+    }
+    else
+    {
+        validateFunc(outInfo, isSupported);
+    }
+
+    if (!isSupported)
+    {
+        return false;
+    }
+
+    armnn::IConnectableLayer* layer = data.m_Network->AddActivationLayer(activationDesc);
+    ARMNN_ASSERT(layer != nullptr);
+    input.Connect(layer->GetInputSlot(0));
+
+    return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
+}
+
+DequantizeResult DequantizeIfRequired(size_t operand_index,
+                                      const Operation& operation,
+                                      const Model& model,
+                                      const ConversionData& data)
+{
+    const Operand* weightsOperand = GetInputOperand(operation, operand_index, model);
+    if (!weightsOperand)
+    {
+        return { nullptr, 0, armnn::TensorInfo(), DequantizeStatus::INVALID_OPERAND };
+    }
+
+    if (IsOperandConstant(*weightsOperand))
+    {
+        // Weights are already constant
+        return { nullptr, 0, armnn::TensorInfo(), DequantizeStatus::NOT_REQUIRED };
+    }
+
+    const size_t weightsInputIndex = operation.inputs[operand_index];
+
+    // The weights are a non const tensor, this indicates they might be the output of a dequantize op.
+    // Iterate over the nodes and find the previous operation which should be DEQUANTIZE
+    for (uint32_t operationIdx = 0; operationIdx < getMainModel(model).operations.size(); ++operationIdx)
+    {
+        // Search for the DEQUANTIZE op which has the operand with index equal to operandIndex
+        const auto& operationIt = getMainModel(model).operations[operationIdx];
+        if (operationIt.type != OperationType::DEQUANTIZE)
+        {
+            continue;
+        }
+
+        size_t outOpIndex = weightsInputIndex + 1;
+        for (size_t i = 0; outOpIndex != weightsInputIndex && i < operationIt.outputs.size(); ++i)
+        {
+            outOpIndex = operationIt.outputs[i];
+        }
+
+        if (outOpIndex != weightsInputIndex)
+        {
+            continue;
+        }
+
+        const Operand* operand = GetInputOperand(operationIt, 0, model);
+        ARMNN_ASSERT(operand);
+
+        if (!IsQSymm8(*operand))
+        {
+            // Only supporting dequantize from QSYMM8 to FLOAT
+            break;
+        }
+
+        // Allocate a new buffer for the dequantized data and manually dequantize
+        const void* startValue = GetOperandValueReadOnlyAddress(*operand, model, data);
+        if (!startValue)
+        {
+            // Failed to get the operand address
+            break;
+        }
+
+        const uint8_t* quantizedBuffer = reinterpret_cast<const uint8_t*>(startValue);
+        size_t dequantizedBufferLength = operand->location.length;
+        const float quantizationScale  = operand->scale;
+
+        auto dequantizedBuffer = std::make_unique<float[]>(dequantizedBufferLength + 1);
+        for (size_t i = 0; i < dequantizedBufferLength; ++i)
+        {
+            float* dstPtr = dequantizedBuffer.get();
+            ARMNN_ASSERT(dstPtr);
+            *dstPtr++ = quantizedBuffer[i] * quantizationScale;
+        }
+
+        // Construct tensor info for dequantized ConstTensor
+        armnn::TensorInfo tensorInfo(operand->dimensions.size(),
+                                     operand->dimensions.data(),
+                                     armnn::DataType::Float32);
+
+        return { std::move(dequantizedBuffer), dequantizedBufferLength * sizeof(float),
+                 std::move(tensorInfo),
+                 DequantizeStatus::SUCCESS };
+    }
+
+    return { nullptr, 0, armnn::TensorInfo() , DequantizeStatus::NOT_REQUIRED};
+}
+
+ConstTensorPin DequantizeAndMakeConstTensorPin(const Operation& operation,
+                                               const Model& model,
+                                               const ConversionData& data,
+                                               size_t operandIndex,
+                                               bool optional)
+{
+    DequantizeResult dequantized = DequantizeIfRequired(operandIndex,operation, model, data);
+
+    DequantizeStatus status = std::get<3>(dequantized);
+    switch (status)
+    {
+        case DequantizeStatus::INVALID_OPERAND:
+        {
+            // return invalid const tensor pin
+            return ConstTensorPin();
+        }
+        case DequantizeStatus::NOT_REQUIRED:
+        {
+            return ConvertOperationInputToConstTensorPin(
+                operation, operandIndex, model, data, g_DontPermute, nullptr, optional);
+        }
+        case DequantizeStatus::SUCCESS:
+        default:
+        {
+            return ConstTensorPin(
+                std::get<2>(dequantized), std::get<0>(dequantized).get(), std::get<1>(dequantized), g_DontPermute);
+        }
+    }
+}
+
+bool GetInputPaddingScheme(const Operation& operation,
+                           uint32_t inputIndex,
+                           PaddingScheme& outPaddingScheme,
+                           const Model& model,
+                           const ConversionData& data)
+{
+    int32_t paddingSchemeAsInt;
+    if (!GetInputInt32(operation, inputIndex, paddingSchemeAsInt, model, data))
+    {
+        return Fail("%s: failed to get padding scheme input value", __func__);
+    }
+
+    outPaddingScheme = static_cast<::android::nn::PaddingScheme>(paddingSchemeAsInt);
+    return true;
+}
+
+const void* GetOperandValueReadOnlyAddress(const Operand& operand,
+                                           const Model& model,
+                                           const ConversionData& data,
+                                           bool optional)
+{
+    const void* valueStart = nullptr;
+    switch (operand.lifetime)
+    {
+        case OperandLifeTime::CONSTANT_COPY:
+        {
+            valueStart = model.operandValues.data() + operand.location.offset;
+            break;
+        }
+        case OperandLifeTime::POINTER:
+        {
+            // Pointer specified in the model
+            valueStart = std::get<const void*>(operand.location.pointer);
+            break;
+        }
+        case OperandLifeTime::CONSTANT_REFERENCE:
+        {
+            // Constant specified via a Memory object
+            valueStart = GetMemoryFromPool(operand.location, data.m_MemPools);
+            break;
+        }
+        case OperandLifeTime::NO_VALUE:
+        {
+            // An optional input tensor with no values is not an error so should not register as a fail
+            if (optional)
+            {
+                valueStart = nullptr;
+                break;
+            }
+            [[fallthrough]];
+        }
+        default:
+        {
+            VLOG(DRIVER) << __func__ << ": unsupported/invalid operand lifetime:: " << operand.lifetime;
+            valueStart = nullptr;
+        }
+    }
+
+    return valueStart;
+}
+
+bool GetTensorInt32Values(const Operand& operand,
+                                 std::vector<int32_t>& outValues,
+                                 const Model& model,
+                                 const ConversionData& data)
+{
+    if (operand.type != OperandType::TENSOR_INT32)
+    {
+        VLOG(DRIVER) << __func__ << ": invalid operand type: " << operand.type;
+        return false;
+    }
+
+    const void* startAddress = GetOperandValueReadOnlyAddress(operand, model, data);
+    if (!startAddress)
+    {
+        VLOG(DRIVER) << __func__ << ": failed to get operand address " << operand.type;
+        return false;
+    }
+
+    // Check number of bytes is sensible
+    const uint32_t numBytes = operand.location.length;
+    if (numBytes % sizeof(int32_t) != 0)
+    {
+        return Fail("%s: invalid number of bytes: %i, expected to be a multiple of %i",
+                    __func__, numBytes, sizeof(int32_t));
+    }
+
+    outValues.resize(numBytes / sizeof(int32_t));
+    memcpy(outValues.data(), startAddress, numBytes);
+    return true;
+}
+
+armnn::DataLayout OptionalDataLayout(const Operation& operation,
+                                     uint32_t inputIndex,
+                                     const Model& model,
+                                     ConversionData& data)
+{
+    const Operand* operand = GetInputOperand(operation, inputIndex, model);
+    if (!operand)
+    {
+        return armnn::DataLayout::NHWC;
+    }
+
+    if (!IsBool(*operand))
+    {
+        return armnn::DataLayout::NHWC;
+    }
+
+    const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
+    if (!valueAddress)
+    {
+        return armnn::DataLayout::NHWC;
+    }
+
+    if (*(static_cast<const bool*>(valueAddress)))
+    {
+        return armnn::DataLayout::NCHW;
+    }
+    else
+    {
+        return armnn::DataLayout::NHWC;
+    }
+}
+
+armnn::IConnectableLayer* ProcessActivation(const armnn::TensorInfo& tensorInfo,
+                                            ActivationFn activation,
+                                            armnn::IConnectableLayer* prevLayer,
+                                            ConversionData& data)
+{
+    ARMNN_ASSERT(prevLayer->GetNumOutputSlots() == 1);
+
+    prevLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
+
+    armnn::IConnectableLayer* activationLayer = prevLayer;
+
+    if (activation != ActivationFn::kActivationNone)
+    {
+        armnn::ActivationDescriptor activationDesc;
+        switch (activation)
+        {
+            case ActivationFn::kActivationRelu:
+            {
+                activationDesc.m_Function = armnn::ActivationFunction::ReLu;
+                break;
+            }
+            case ActivationFn::kActivationRelu1:
+            {
+                activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
+                activationDesc.m_A = 1.0f;
+                activationDesc.m_B = -1.0f;
+                break;
+            }
+            case ActivationFn::kActivationRelu6:
+            {
+                activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
+                activationDesc.m_A = 6.0f;
+                break;
+            }
+            case ActivationFn::kActivationSigmoid:
+            {
+                activationDesc.m_Function = armnn::ActivationFunction::Sigmoid;
+                break;
+            }
+            case ActivationFn::kActivationTanh:
+            {
+                activationDesc.m_Function = armnn::ActivationFunction::TanH;
+                activationDesc.m_A = 1.0f;
+                activationDesc.m_B = 1.0f;
+                break;
+            }
+            default:
+            {
+                Fail("%s: Invalid activation enum value %i", __func__, activation);
+                return nullptr;
+            }
+        }
+
+        bool isSupported = false;
+        FORWARD_LAYER_SUPPORT_FUNC(__func__,
+                                   IsActivationSupported,
+                                   data.m_Backends,
+                                   isSupported,
+                                   prevLayer->GetOutputSlot(0).GetTensorInfo(),
+                                   tensorInfo,
+                                   activationDesc);
+        if (!isSupported)
+        {
+            return nullptr;
+        }
+
+        activationLayer = data.m_Network->AddActivationLayer(activationDesc);
+
+        prevLayer->GetOutputSlot(0).Connect(activationLayer->GetInputSlot(0));
+        activationLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
+    }
+
+    return activationLayer;
+}
+
+bool SetupAndTrackLayerOutputSlot(const Operation& operation,
+                                  uint32_t operationOutputIndex,
+                                  armnn::IConnectableLayer& layer,
+                                  uint32_t layerOutputIndex,
+                                  const Model& model,
+                                  ConversionData& data,
+                                  const armnn::TensorInfo* overrideOutputInfo,
+                                  const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc,
+                                  const ActivationFn& activationFunction,
+                                  bool inferOutputShapes)
+{
+    const Operand* outputOperand = GetOutputOperand(operation, operationOutputIndex, model);
+    if ((outputOperand == nullptr) || (operationOutputIndex >= layer.GetNumOutputSlots()))
+    {
+        return false;
+    }
+
+    armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex);
+    if (overrideOutputInfo == nullptr)
+    {
+        outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand));
+    }
+    else
+    {
+        outputSlot.SetTensorInfo(*overrideOutputInfo);
+    }
+
+    bool isSupported = false;
+    if (validateFunc && (IsDynamicTensor(outputSlot.GetTensorInfo()) || inferOutputShapes))
+    {
+        // Type one dynamic tensors require the previous layer's output shape for inference
+        for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex)
+        {
+            if(!layer.GetInputSlot(inputSlotIndex).GetConnection())
+            {
+                return false;
+            }
+        }
+        // IsTensorInfoSet will infer the dynamic output shape
+        outputSlot.IsTensorInfoSet();
+        // Once the shape is inferred we can validate it
+        validateFunc(outputSlot.GetTensorInfo(), isSupported);
+
+        if(!isSupported)
+        {
+            for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex)
+            {
+                layer.GetInputSlot(inputSlotIndex).GetConnection()->Disconnect(layer.GetInputSlot(inputSlotIndex));
+            }
+            return false;
+        }
+    }
+
+    const uint32_t operandIndex = operation.outputs[operationOutputIndex];
+
+    if (activationFunction != ActivationFn::kActivationNone)
+    {
+        const armnn::TensorInfo& activationOutputInfo = outputSlot.GetTensorInfo();
+        armnn::IConnectableLayer* const endLayer = ProcessActivation(activationOutputInfo, activationFunction,
+                                                                     &layer, data);
+
+        if (!endLayer)
+        {
+            return Fail("%s: ProcessActivation failed", __func__);
+        }
+
+        armnn::IOutputSlot& activationOutputSlot = endLayer->GetOutputSlot(layerOutputIndex);
+        data.m_OutputSlotForOperand[operandIndex] = &activationOutputSlot;
+    }
+    else
+    {
+        data.m_OutputSlotForOperand[operandIndex] = &outputSlot;
+    }
+
+    return true;
+}
+
+} // namespace armnn_driver