Add QoS types to NNAPI runtime

Bug: 136739795
Bug: 142902514
Bug: 145300530
Test: mma
Change-Id: If3ce646d512b02daa78479aa7c75de99e20def21
diff --git a/nn/common/ExecutionBurstController.cpp b/nn/common/ExecutionBurstController.cpp
index 1136024..eb956de 100644
--- a/nn/common/ExecutionBurstController.cpp
+++ b/nn/common/ExecutionBurstController.cpp
@@ -144,7 +144,7 @@
 }
 
 // deserialize a packet into the result
-std::optional<std::tuple<ErrorStatus, std::vector<OutputShape>, Timing>> deserialize(
+std::optional<std::tuple<V1_0::ErrorStatus, std::vector<OutputShape>, Timing>> deserialize(
         const std::vector<FmqResultDatum>& data) {
     using discriminator = FmqResultDatum::hidl_discriminator;
 
@@ -161,7 +161,7 @@
     const FmqResultDatum::PacketInformation& packetInfo = data[index].packetInformation();
     index++;
     const uint32_t packetSize = packetInfo.packetSize;
-    const ErrorStatus errorStatus = packetInfo.errorStatus;
+    const V1_0::ErrorStatus errorStatus = packetInfo.errorStatus;
     const uint32_t numberOfOperands = packetInfo.numberOfOperands;
 
     // verify packet size
@@ -245,7 +245,7 @@
                                              std::chrono::microseconds pollingTimeWindow)
     : mFmqResultChannel(std::move(fmqResultChannel)), kPollingTimeWindow(pollingTimeWindow) {}
 
-std::optional<std::tuple<ErrorStatus, std::vector<OutputShape>, Timing>>
+std::optional<std::tuple<V1_0::ErrorStatus, std::vector<OutputShape>, Timing>>
 ResultChannelReceiver::getBlocking() {
     const auto packet = getPacketBlocking();
     if (!packet) {
@@ -266,7 +266,7 @@
     // TODO: look for a different/better way to signal/notify the futex to
     // wake up any thread waiting on it
     FmqResultDatum datum;
-    datum.packetInformation({/*.packetSize=*/0, /*.errorStatus=*/ErrorStatus::GENERAL_FAILURE,
+    datum.packetInformation({/*.packetSize=*/0, /*.errorStatus=*/V1_0::ErrorStatus::GENERAL_FAILURE,
                              /*.numberOfOperands=*/0});
     mFmqResultChannel->writeBlocking(&datum, 1);
 }
@@ -395,12 +395,12 @@
     // ensure all memories are valid
     if (!std::all_of(memories.begin(), memories.end(),
                      [](const hidl_memory& memory) { return memory.valid(); })) {
-        cb(ErrorStatus::INVALID_ARGUMENT, {});
+        cb(V1_0::ErrorStatus::INVALID_ARGUMENT, {});
         return Void();
     }
 
     // return successful
-    cb(ErrorStatus::NONE, std::move(memories));
+    cb(V1_0::ErrorStatus::NONE, std::move(memories));
     return Void();
 }
 
@@ -494,11 +494,12 @@
     }
 
     // configure burst
-    ErrorStatus errorStatus;
+    V1_0::ErrorStatus errorStatus;
     sp<IBurstContext> burstContext;
     const Return<void> ret = preparedModel->configureExecutionBurst(
             callback, *requestChannelDescriptor, *resultChannelDescriptor,
-            [&errorStatus, &burstContext](ErrorStatus status, const sp<IBurstContext>& context) {
+            [&errorStatus, &burstContext](V1_0::ErrorStatus status,
+                                          const sp<IBurstContext>& context) {
                 errorStatus = status;
                 burstContext = context;
             });
@@ -509,7 +510,7 @@
                    << ret.description();
         return nullptr;
     }
-    if (errorStatus != ErrorStatus::NONE) {
+    if (errorStatus != V1_0::ErrorStatus::NONE) {
         LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with status "
                    << toString(errorStatus);
         return nullptr;
@@ -565,9 +566,10 @@
 }
 
 static std::tuple<int, std::vector<OutputShape>, Timing, bool> getExecutionResult(
-        ErrorStatus status, std::vector<OutputShape> outputShapes, Timing timing, bool fallback) {
+        V1_0::ErrorStatus status, std::vector<OutputShape> outputShapes, Timing timing,
+        bool fallback) {
     auto [n, checkedOutputShapes, checkedTiming] =
-            getExecutionResult(status, std::move(outputShapes), timing);
+            getExecutionResult(convertToV1_3(status), std::move(outputShapes), timing);
     return {n, std::move(checkedOutputShapes), checkedTiming, fallback};
 }
 
@@ -589,7 +591,8 @@
     if (!success) {
         LOG(ERROR) << "Error sending FMQ packet";
         // only use fallback execution path if the packet could not be sent
-        return getExecutionResult(ErrorStatus::GENERAL_FAILURE, {}, kNoTiming, /*fallback=*/true);
+        return getExecutionResult(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming,
+                                  /*fallback=*/true);
     }
 
     // get result packet
@@ -597,7 +600,8 @@
     if (!result) {
         LOG(ERROR) << "Error retrieving FMQ packet";
         // only use fallback execution path if the packet could not be sent
-        return getExecutionResult(ErrorStatus::GENERAL_FAILURE, {}, kNoTiming, /*fallback=*/false);
+        return getExecutionResult(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming,
+                                  /*fallback=*/false);
     }
 
     // unpack results and return (only use fallback execution path if the
diff --git a/nn/common/ExecutionBurstServer.cpp b/nn/common/ExecutionBurstServer.cpp
index 890b653..7cd130f 100644
--- a/nn/common/ExecutionBurstServer.cpp
+++ b/nn/common/ExecutionBurstServer.cpp
@@ -62,7 +62,7 @@
 
     void removeCacheEntry(int32_t slot) override { mMemoryCache.erase(slot); }
 
-    std::tuple<ErrorStatus, hidl_vec<OutputShape>, Timing> execute(
+    std::tuple<V1_0::ErrorStatus, hidl_vec<OutputShape>, Timing> execute(
             const V1_0::Request& request, const std::vector<int32_t>& slots,
             MeasureTiming measure) override {
         // convert slots to pools
@@ -75,11 +75,11 @@
         fullRequest.pools = std::move(pools);
 
         // setup execution
-        ErrorStatus returnedStatus = ErrorStatus::GENERAL_FAILURE;
+        V1_0::ErrorStatus returnedStatus = V1_0::ErrorStatus::GENERAL_FAILURE;
         hidl_vec<OutputShape> returnedOutputShapes;
         Timing returnedTiming;
         auto cb = [&returnedStatus, &returnedOutputShapes, &returnedTiming](
-                          ErrorStatus status, const hidl_vec<OutputShape>& outputShapes,
+                          V1_0::ErrorStatus status, const hidl_vec<OutputShape>& outputShapes,
                           const Timing& timing) {
             returnedStatus = status;
             returnedOutputShapes = outputShapes;
@@ -88,7 +88,7 @@
 
         // execute
         const Return<void> ret = mpPreparedModel->executeSynchronously(fullRequest, measure, cb);
-        if (!ret.isOk() || returnedStatus != ErrorStatus::NONE) {
+        if (!ret.isOk() || returnedStatus != V1_0::ErrorStatus::NONE) {
             LOG(ERROR) << "IPreparedModelAdapter::execute -- Error executing";
             return {returnedStatus, {}, kNoTiming};
         }
@@ -104,7 +104,7 @@
 }  // anonymous namespace
 
 // serialize result
-std::vector<FmqResultDatum> serialize(ErrorStatus errorStatus,
+std::vector<FmqResultDatum> serialize(V1_0::ErrorStatus errorStatus,
                                       const std::vector<OutputShape>& outputShapes, Timing timing) {
     // count how many elements need to be sent for a request
     size_t count = 2 + outputShapes.size();
@@ -458,7 +458,7 @@
 ResultChannelSender::ResultChannelSender(std::unique_ptr<FmqResultChannel> fmqResultChannel)
     : mFmqResultChannel(std::move(fmqResultChannel)) {}
 
-bool ResultChannelSender::send(ErrorStatus errorStatus,
+bool ResultChannelSender::send(V1_0::ErrorStatus errorStatus,
                                const std::vector<OutputShape>& outputShapes, Timing timing) {
     const std::vector<FmqResultDatum> serialized = serialize(errorStatus, outputShapes, timing);
     return sendPacket(serialized);
@@ -469,7 +469,7 @@
         LOG(ERROR)
                 << "ResultChannelSender::sendPacket -- packet size exceeds size available in FMQ";
         const std::vector<FmqResultDatum> errorPacket =
-                serialize(ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+                serialize(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
 
         // Always send the packet with "blocking" because this signals the futex
         // and unblocks the consumer if it is waiting on the futex.
@@ -575,9 +575,9 @@
         return;
     }
 
-    ErrorStatus errorStatus = ErrorStatus::GENERAL_FAILURE;
+    V1_0::ErrorStatus errorStatus = V1_0::ErrorStatus::GENERAL_FAILURE;
     std::vector<hidl_memory> returnedMemories;
-    auto cb = [&errorStatus, &returnedMemories](ErrorStatus status,
+    auto cb = [&errorStatus, &returnedMemories](V1_0::ErrorStatus status,
                                                 const hidl_vec<hidl_memory>& memories) {
         errorStatus = status;
         returnedMemories = memories;
@@ -585,7 +585,7 @@
 
     const Return<void> ret = mCallback->getMemories(unknownSlots, cb);
 
-    if (!ret.isOk() || errorStatus != ErrorStatus::NONE ||
+    if (!ret.isOk() || errorStatus != V1_0::ErrorStatus::NONE ||
         returnedMemories.size() != unknownSlots.size()) {
         LOG(ERROR) << "Error retrieving memories";
         return;
@@ -610,7 +610,7 @@
         // "task" function can end
         if (!arguments) {
             if (!mTeardown) {
-                mResultChannelSender->send(ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+                mResultChannelSender->send(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
             }
             continue;
         }
diff --git a/nn/common/Utils.cpp b/nn/common/Utils.cpp
index 195b804..f753a16 100644
--- a/nn/common/Utils.cpp
+++ b/nn/common/Utils.cpp
@@ -1658,40 +1658,51 @@
         case ANEURALNETWORKS_UNAVAILABLE_DEVICE:
             return ErrorStatus::DEVICE_UNAVAILABLE;
 
-        default:
-            LOG(ERROR) << "Unknown result code " << resultCode
-                       << " mapped to ErrorStatus::GENERAL_FAILURE";
-            return ErrorStatus::GENERAL_FAILURE;
         case ANEURALNETWORKS_BAD_STATE:
         case ANEURALNETWORKS_INCOMPLETE:
         case ANEURALNETWORKS_OP_FAILED:
         case ANEURALNETWORKS_OUT_OF_MEMORY:
         case ANEURALNETWORKS_UNMAPPABLE:
+        case ANEURALNETWORKS_DEAD_OBJECT:
             return ErrorStatus::GENERAL_FAILURE;
+
+        case ANEURALNETWORKS_MISSED_DEADLINE_TRANSIENT:
+            return ErrorStatus::MISSED_DEADLINE_TRANSIENT;
+        case ANEURALNETWORKS_MISSED_DEADLINE_PERSISTENT:
+            return ErrorStatus::MISSED_DEADLINE_PERSISTENT;
+        case ANEURALNETWORKS_RESOURCE_EXHAUSTED_TRANSIENT:
+            return ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT;
+        case ANEURALNETWORKS_RESOURCE_EXHAUSTED_PERSISTENT:
+            return ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT;
     }
+    LOG(ERROR) << "Unknown result code " << resultCode << " mapped to ErrorStatus::GENERAL_FAILURE";
+    return ErrorStatus::GENERAL_FAILURE;
 }
 
 int convertErrorStatusToResultCode(ErrorStatus status) {
     switch (status) {
         case ErrorStatus::NONE:
             return ANEURALNETWORKS_NO_ERROR;
-
-        case ErrorStatus::INVALID_ARGUMENT:
-            return ANEURALNETWORKS_BAD_DATA;
-
-        case ErrorStatus::OUTPUT_INSUFFICIENT_SIZE:
-            return ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE;
-
         case ErrorStatus::DEVICE_UNAVAILABLE:
             return ANEURALNETWORKS_UNAVAILABLE_DEVICE;
-
-        default:
-            LOG(ERROR) << "Unknown ErrorStatus " << toString(status)
-                       << " mapped to ANEURALNETWORKS_OP_FAILED";
-            return ANEURALNETWORKS_OP_FAILED;
         case ErrorStatus::GENERAL_FAILURE:
             return ANEURALNETWORKS_OP_FAILED;
+        case ErrorStatus::OUTPUT_INSUFFICIENT_SIZE:
+            return ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE;
+        case ErrorStatus::INVALID_ARGUMENT:
+            return ANEURALNETWORKS_BAD_DATA;
+        case ErrorStatus::MISSED_DEADLINE_TRANSIENT:
+            return ANEURALNETWORKS_MISSED_DEADLINE_TRANSIENT;
+        case ErrorStatus::MISSED_DEADLINE_PERSISTENT:
+            return ANEURALNETWORKS_MISSED_DEADLINE_PERSISTENT;
+        case ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT:
+            return ANEURALNETWORKS_RESOURCE_EXHAUSTED_TRANSIENT;
+        case ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT:
+            return ANEURALNETWORKS_RESOURCE_EXHAUSTED_PERSISTENT;
     }
+    LOG(ERROR) << "Unknown ErrorStatus " << toString(status)
+               << " mapped to ANEURALNETWORKS_OP_FAILED";
+    return ANEURALNETWORKS_OP_FAILED;
 }
 
 std::tuple<int, std::vector<OutputShape>, Timing> getExecutionResult(
@@ -1950,6 +1961,43 @@
     return true;
 }
 
+V1_0::ErrorStatus convertToV1_0(V1_0::ErrorStatus status) {
+    return status;
+}
+
+V1_0::ErrorStatus convertToV1_0(V1_3::ErrorStatus status) {
+    switch (status) {
+        case V1_3::ErrorStatus::NONE:
+            return V1_0::ErrorStatus::NONE;
+        case V1_3::ErrorStatus::DEVICE_UNAVAILABLE:
+            return V1_0::ErrorStatus::DEVICE_UNAVAILABLE;
+        case V1_3::ErrorStatus::GENERAL_FAILURE:
+            return V1_0::ErrorStatus::GENERAL_FAILURE;
+        case V1_3::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE:
+            return V1_0::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
+        case V1_3::ErrorStatus::INVALID_ARGUMENT:
+            return V1_0::ErrorStatus::INVALID_ARGUMENT;
+        case V1_3::ErrorStatus::MISSED_DEADLINE_TRANSIENT:
+            return V1_0::ErrorStatus::GENERAL_FAILURE;
+        case V1_3::ErrorStatus::MISSED_DEADLINE_PERSISTENT:
+            return V1_0::ErrorStatus::GENERAL_FAILURE;
+        case V1_3::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT:
+            return V1_0::ErrorStatus::GENERAL_FAILURE;
+        case V1_3::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT:
+            return V1_0::ErrorStatus::GENERAL_FAILURE;
+    }
+    LOG(ERROR) << "Unknown ErrorStatus: " << toString(status) << " mapped to GENERAL_FAILURE";
+    return V1_0::ErrorStatus::GENERAL_FAILURE;
+}
+
+V1_3::ErrorStatus convertToV1_3(V1_0::ErrorStatus status) {
+    return static_cast<V1_3::ErrorStatus>(status);
+}
+
+V1_3::ErrorStatus convertToV1_3(V1_3::ErrorStatus status) {
+    return status;
+}
+
 static V1_0::OperationType uncheckedConvertToV1_0(V1_1::OperationType type) {
     return static_cast<V1_0::OperationType>(type);
 }
diff --git a/nn/common/include/ExecutionBurstController.h b/nn/common/include/ExecutionBurstController.h
index 15db0fc..e8f3657 100644
--- a/nn/common/include/ExecutionBurstController.h
+++ b/nn/common/include/ExecutionBurstController.h
@@ -64,8 +64,8 @@
  * @param data Serialized FMQ result data.
  * @return Result object if successfully deserialized, std::nullopt otherwise.
  */
-std::optional<std::tuple<hal::ErrorStatus, std::vector<hal::OutputShape>, hal::Timing>> deserialize(
-        const std::vector<hal::FmqResultDatum>& data);
+std::optional<std::tuple<hal::V1_0::ErrorStatus, std::vector<hal::OutputShape>, hal::Timing>>
+deserialize(const std::vector<hal::FmqResultDatum>& data);
 
 /**
  * ResultChannelReceiver is responsible for waiting on the channel until the
@@ -108,7 +108,7 @@
      * @return Result object if successfully received, std::nullopt if error or
      *     if the receiver object was invalidated.
      */
-    std::optional<std::tuple<hal::ErrorStatus, std::vector<hal::OutputShape>, hal::Timing>>
+    std::optional<std::tuple<hal::V1_0::ErrorStatus, std::vector<hal::OutputShape>, hal::Timing>>
     getBlocking();
 
     /**
diff --git a/nn/common/include/ExecutionBurstServer.h b/nn/common/include/ExecutionBurstServer.h
index 5bac095..2a0dfba 100644
--- a/nn/common/include/ExecutionBurstServer.h
+++ b/nn/common/include/ExecutionBurstServer.h
@@ -46,7 +46,7 @@
  * @param timing Timing information of the execution.
  * @return Serialized FMQ result data.
  */
-std::vector<hal::FmqResultDatum> serialize(hal::ErrorStatus errorStatus,
+std::vector<hal::FmqResultDatum> serialize(hal::V1_0::ErrorStatus errorStatus,
                                            const std::vector<hal::OutputShape>& outputShapes,
                                            hal::Timing timing);
 
@@ -151,7 +151,7 @@
      * @param timing Timing information of the execution.
      * @return 'true' on successful send, 'false' otherwise.
      */
-    bool send(hal::ErrorStatus errorStatus, const std::vector<hal::OutputShape>& outputShapes,
+    bool send(hal::V1_0::ErrorStatus errorStatus, const std::vector<hal::OutputShape>& outputShapes,
               hal::Timing timing);
 
     // prefer calling ResultChannelSender::send
@@ -233,8 +233,8 @@
          * @return Result of the execution, including the status of the
          *     execution, dynamic output shapes, and any timing information.
          */
-        virtual std::tuple<hal::ErrorStatus, hal::hidl_vec<hal::OutputShape>, hal::Timing> execute(
-                const hal::V1_0::Request& request, const std::vector<int32_t>& slots,
+        virtual std::tuple<hal::V1_0::ErrorStatus, hal::hidl_vec<hal::OutputShape>, hal::Timing>
+        execute(const hal::V1_0::Request& request, const std::vector<int32_t>& slots,
                 hal::MeasureTiming measure) = 0;
     };
 
diff --git a/nn/common/include/HalInterfaces.h b/nn/common/include/HalInterfaces.h
index 8efbf21..fc18e2b 100644
--- a/nn/common/include/HalInterfaces.h
+++ b/nn/common/include/HalInterfaces.h
@@ -30,6 +30,7 @@
 #include <android/hardware/neuralnetworks/1.2/IPreparedModelCallback.h>
 #include <android/hardware/neuralnetworks/1.2/types.h>
 #include <android/hardware/neuralnetworks/1.3/IDevice.h>
+#include <android/hardware/neuralnetworks/1.3/IExecutionCallback.h>
 #include <android/hardware/neuralnetworks/1.3/IPreparedModel.h>
 #include <android/hardware/neuralnetworks/1.3/IPreparedModelCallback.h>
 #include <android/hardware/neuralnetworks/1.3/types.h>
@@ -60,7 +61,6 @@
 
 using V1_0::DataLocation;
 using V1_0::DeviceStatus;
-using V1_0::ErrorStatus;
 using V1_0::FusedActivationFunc;
 using V1_0::PerformanceInfo;
 using V1_0::RequestArgument;
@@ -72,7 +72,6 @@
 using V1_2::FmqResultDatum;
 using V1_2::IBurstCallback;
 using V1_2::IBurstContext;
-using V1_2::IExecutionCallback;
 using V1_2::MeasureTiming;
 using V1_2::OutputShape;
 using V1_2::SymmPerChannelQuantParams;
@@ -80,8 +79,10 @@
 using V1_3::BufferDesc;
 using V1_3::BufferRole;
 using V1_3::Capabilities;
+using V1_3::ErrorStatus;
 using V1_3::IBuffer;
 using V1_3::IDevice;
+using V1_3::IExecutionCallback;
 using V1_3::IPreparedModel;
 using V1_3::IPreparedModelCallback;
 using V1_3::Model;
@@ -92,6 +93,8 @@
 using V1_3::Operation;
 using V1_3::OperationType;
 using V1_3::OperationTypeRange;
+using V1_3::OptionalTimePoint;
+using V1_3::Priority;
 using V1_3::Request;
 using V1_3::Subgraph;
 using ExtensionNameAndPrefix = V1_2::Model::ExtensionNameAndPrefix;
@@ -101,6 +104,8 @@
         hardware::hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
 using ModelFactory = std::function<Model()>;
 
+inline constexpr Priority kDefaultPriority = Priority::MEDIUM;
+
 }  // namespace android::nn::hal
 
 #endif  // ANDROID_FRAMEWORKS_ML_NN_COMMON_HAL_INTERFACES_H
diff --git a/nn/common/include/Utils.h b/nn/common/include/Utils.h
index ada19fc..2d341ef 100644
--- a/nn/common/include/Utils.h
+++ b/nn/common/include/Utils.h
@@ -22,6 +22,7 @@
 #include <set>
 #include <string>
 #include <tuple>
+#include <utility>
 #include <vector>
 
 #include "HalInterfaces.h"
@@ -385,6 +386,11 @@
 bool compliantWithV1_2(const hal::V1_3::Model& model,
                        std::set<uint32_t>* noncompliantOperations = nullptr);
 
+hal::V1_0::ErrorStatus convertToV1_0(hal::V1_0::ErrorStatus status);
+hal::V1_0::ErrorStatus convertToV1_0(hal::V1_3::ErrorStatus status);
+hal::V1_3::ErrorStatus convertToV1_3(hal::V1_0::ErrorStatus status);
+hal::V1_3::ErrorStatus convertToV1_3(hal::V1_3::ErrorStatus status);
+
 hal::V1_0::Capabilities convertToV1_0(const hal::V1_0::Capabilities& capabilities);
 hal::V1_0::Capabilities convertToV1_0(const hal::V1_1::Capabilities& capabilities);
 hal::V1_0::Capabilities convertToV1_0(const hal::V1_2::Capabilities& capabilities);
@@ -459,6 +465,19 @@
 hal::V1_3::OperandLifeTime convertToV1_3(hal::V1_0::OperandLifeTime lifetime);
 hal::V1_3::OperandLifeTime convertToV1_3(hal::V1_3::OperandLifeTime lifetime);
 
+constexpr hal::Priority convertToHalPriority(int32_t priority) {
+    switch (priority) {
+        case ANEURALNETWORKS_PRIORITY_LOW:
+            return hal::Priority::LOW;
+        case ANEURALNETWORKS_PRIORITY_MEDIUM:
+            return hal::Priority::MEDIUM;
+        case ANEURALNETWORKS_PRIORITY_HIGH:
+            return hal::Priority::HIGH;
+    }
+    LOG(FATAL) << "unrecognized priority: " << priority;
+    return {};
+}
+
 #ifdef NN_DEBUGGABLE
 uint32_t getProp(const char* str, uint32_t defaultValue = 0);
 #endif  // NN_DEBUGGABLE