Merge "Update libneuralnetworks_driver_fuzzer"
diff --git a/common/Android.bp b/common/Android.bp
index f36105d..13aae0e 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -129,7 +129,6 @@
         "CpuExecutor.cpp",
         "ExecutionBurstController.cpp",
         "ExecutionBurstServer.cpp",
-        "FlagUtils.cpp",
         "GraphDump.cpp",
         "HalBufferTracker.cpp",
         "IndexedShapeWrapper.cpp",
@@ -275,7 +274,6 @@
     srcs: [
         "BufferTracker.cpp",
         "CpuExecutor.cpp",
-        "FlagUtils.cpp",
         "GraphDump.cpp",
         "IndexedShapeWrapper.cpp",
         "LegacyUtils.cpp",
diff --git a/common/FlagUtils.cpp b/common/FlagUtils.cpp
deleted file mode 100644
index cb87ef7..0000000
--- a/common/FlagUtils.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "FlagUtils"
-
-#include "FlagUtils.h"
-
-namespace android {
-namespace nn {
-
-namespace {
-int64_t getFeatureLevelFlag() {
-    if (_getServerFeatureLevelFlag) {
-        return _getServerFeatureLevelFlag();
-    }
-    return kDefaultFeatureLevelNum;
-}
-
-FeatureLevelCode flagToFeatureLevelCode(int64_t featureLevelFlag) {
-    switch (featureLevelFlag) {
-        case 1:
-            return ANEURALNETWORKS_FEATURE_LEVEL_1;
-        case 2:
-            return ANEURALNETWORKS_FEATURE_LEVEL_2;
-        case 3:
-            return ANEURALNETWORKS_FEATURE_LEVEL_3;
-        case 4:
-            return ANEURALNETWORKS_FEATURE_LEVEL_4;
-        case 5:
-            return ANEURALNETWORKS_FEATURE_LEVEL_5;
-        case 6:
-            return ANEURALNETWORKS_FEATURE_LEVEL_6;
-        case 7:
-            return ANEURALNETWORKS_FEATURE_LEVEL_7;
-        default:
-            return KDefaultFeatureLevelCode;
-    }
-}
-}  // namespace
-
-FeatureLevelCode queryFeatureLevel() {
-    static const int64_t featureLevelFlag = getFeatureLevelFlag();
-    return flagToFeatureLevelCode(featureLevelFlag);
-}
-
-}  // namespace nn
-}  // namespace android
diff --git a/common/TypeUtils.cpp b/common/TypeUtils.cpp
index a8b76a7..31639b8 100644
--- a/common/TypeUtils.cpp
+++ b/common/TypeUtils.cpp
@@ -908,8 +908,8 @@
     return os << optionalTimeoutDuration.value();
 }
 
-static std::ostream& operator<<(std::ostream& os, const Version::Level& level) {
-    switch (level) {
+std::ostream& operator<<(std::ostream& os, const Version::Level& versionLevel) {
+    switch (versionLevel) {
         case Version::Level::FEATURE_LEVEL_1:
             return os << "FEATURE_LEVEL_1";
         case Version::Level::FEATURE_LEVEL_2:
@@ -929,7 +929,7 @@
             return os << "FEATURE_LEVEL_EXPERIMENTAL";
 #endif  // NN_EXPERIMENTAL_FEATURE
     }
-    return os << "Version{" << static_cast<uint32_t>(underlyingType(level)) << "}";
+    return os << "Version{" << static_cast<uint32_t>(underlyingType(versionLevel)) << "}";
 }
 
 std::ostream& operator<<(std::ostream& os, const Version& version) {
diff --git a/common/include/FlagUtils.h b/common/include/FlagUtils.h
deleted file mode 100644
index 02f7fe3..0000000
--- a/common/include/FlagUtils.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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_PACKAGES_MODULES_NEURALNETWORKS_COMMON_FLAG_UTILS_H
-#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_COMMON_FLAG_UTILS_H
-
-#include <stdint.h>
-
-#include "NeuralNetworks.h"
-
-namespace android {
-namespace nn {
-
-// Keep these values consistent with server side configuration in
-// google3/googledata/experiments/mobile/android_platform/nnapi_native/features/feature_level.gcl.
-constexpr char kExprCategoryName[] = "nnapi_native";
-constexpr char kCurrentFeatureLevelFlagName[] = "current_feature_level";
-constexpr int64_t kDefaultFeatureLevelNum = 5;
-constexpr FeatureLevelCode KDefaultFeatureLevelCode = ANEURALNETWORKS_FEATURE_LEVEL_5;
-constexpr int64_t kMinFeatureLevelNum = 5;
-constexpr int64_t kMaxFeatureLevelNum = 7;
-
-// Weak symbol to get server feature level flag so that other targets with different build options
-// (e.g. not vendor available) can implement this function.
-// Note that this function should NOT be used directly and may not be present in the final artifact.
-// Clients are expected to use queryFeatureLevel instead.
-int64_t _getServerFeatureLevelFlag() __attribute__((weak));
-
-// Queries system flag for the current feature level.
-FeatureLevelCode queryFeatureLevel();
-
-}  // namespace nn
-}  // namespace android
-
-#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_COMMON_FLAG_UTILS_H
diff --git a/common/include/nnapi/TypeUtils.h b/common/include/nnapi/TypeUtils.h
index 7cc4ea9..a9b302b 100644
--- a/common/include/nnapi/TypeUtils.h
+++ b/common/include/nnapi/TypeUtils.h
@@ -128,6 +128,7 @@
 std::ostream& operator<<(std::ostream& os, const OptionalTimePoint& optionalTimePoint);
 std::ostream& operator<<(std::ostream& os, const Duration& timeoutDuration);
 std::ostream& operator<<(std::ostream& os, const OptionalDuration& optionalTimeoutDuration);
+std::ostream& operator<<(std::ostream& os, const Version::Level& versionLevel);
 std::ostream& operator<<(std::ostream& os, const Version& version);
 
 bool operator==(const Timing& a, const Timing& b);
diff --git a/common/operations/Dequantize.cpp b/common/operations/Dequantize.cpp
index a9a5e67..5fbc213 100644
--- a/common/operations/Dequantize.cpp
+++ b/common/operations/Dequantize.cpp
@@ -39,6 +39,7 @@
     const float scale = inputShape.scale;
     for (int i = 0; i < numElements; ++i) {
         const int32_t value = inputData[i];
+        // This dequantization formula also appears in Elementwise.cpp.
         outputData[i] = static_cast<OutputType>(scale * (value - zeroPoint));
     }
     return true;
diff --git a/common/operations/Elementwise.cpp b/common/operations/Elementwise.cpp
index a84e24e..9088a2b 100644
--- a/common/operations/Elementwise.cpp
+++ b/common/operations/Elementwise.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "Operations"
 
 #include <cmath>
+#include <functional>
+#include <limits>
 
 #include "OperationResolver.h"
 #include "OperationsUtils.h"
@@ -35,8 +37,8 @@
 namespace {
 
 template <typename IntermediateType, typename T>
-inline bool compute(IntermediateType func(IntermediateType), const T* input, const Shape& shape,
-                    T* output) {
+inline bool compute(const std::function<IntermediateType(IntermediateType)>& func, const T* input,
+                    const Shape& shape, T* output) {
     const auto size = getNumberOfElements(shape);
     for (uint32_t i = 0; i < size; ++i) {
         output[i] = static_cast<T>(func(static_cast<IntermediateType>(input[i])));
@@ -44,6 +46,34 @@
     return true;
 }
 
+template <typename IntermediateType, typename T>
+inline bool compute(IntermediateType func(IntermediateType), const T* input, const Shape& shape,
+                    T* output) {
+    return compute(std::function<IntermediateType(IntermediateType)>(func), input, shape, output);
+}
+
+template <typename IntermediateType, typename T>
+auto makeQuantized(const std::function<IntermediateType(IntermediateType)>& func, float inScale,
+                   T inZeroPoint, float outScale, T outZeroPoint) {
+    return [func, inScale, inZeroPoint, outScale, outZeroPoint](T val) -> T {
+        // For dequantization formula, see Dequantize.cpp.
+        using WideT = int32_t;
+        static_assert(sizeof(T) < sizeof(WideT));
+        IntermediateType dequantizedVal =
+                (static_cast<WideT>(val) - static_cast<WideT>(inZeroPoint)) * inScale;
+
+        IntermediateType res = func(dequantizedVal);
+
+        // For quantization formula, see Quantize.cpp.
+        T quantizedRes = static_cast<T>(std::max<float>(
+                static_cast<IntermediateType>(std::numeric_limits<T>::min()),
+                std::min<float>(static_cast<IntermediateType>(std::numeric_limits<T>::max()),
+                                outZeroPoint + std::round(res / outScale))));
+
+        return quantizedRes;
+    };
+}
+
 bool execute(IOperationExecutionContext* context, float func(float)) {
     switch (context->getInputType(kInputTensor)) {
         case OperandType::TENSOR_FLOAT16:
@@ -82,6 +112,44 @@
     }
 }
 
+bool executeRsqrt(IOperationExecutionContext* context) {
+    const std::function<float(float)> frsqrt = [](float x) { return 1.f / std::sqrt(x); };
+    const auto tensorType = context->getInputType(kInputTensor);
+    switch (tensorType) {
+        case OperandType::TENSOR_FLOAT16:
+            return compute<float, _Float16>(frsqrt, context->getInputBuffer<_Float16>(kInputTensor),
+                                            context->getInputShape(kInputTensor),
+                                            context->getOutputBuffer<_Float16>(kOutputTensor));
+        case OperandType::TENSOR_FLOAT32:
+            return compute<float, float>(frsqrt, context->getInputBuffer<float>(kInputTensor),
+                                         context->getInputShape(kInputTensor),
+                                         context->getOutputBuffer<float>(kOutputTensor));
+        case OperandType::TENSOR_QUANT8_ASYMM: {
+            const Shape inShape = context->getInputShape(kInputTensor);
+            const Shape outShape = context->getOutputShape(kOutputTensor);
+            return compute<uint8_t, uint8_t>(
+                    makeQuantized(frsqrt, inShape.scale, static_cast<uint8_t>(inShape.offset),
+                                  outShape.scale, static_cast<uint8_t>(outShape.offset)),
+                    context->getInputBuffer<uint8_t>(kInputTensor),
+                    context->getInputShape(kInputTensor),
+                    context->getOutputBuffer<uint8_t>(kOutputTensor));
+        }
+        case OperandType::TENSOR_QUANT8_ASYMM_SIGNED: {
+            const Shape inShape = context->getInputShape(kInputTensor);
+            const Shape outShape = context->getOutputShape(kOutputTensor);
+            return compute<int8_t, int8_t>(
+                    makeQuantized(frsqrt, inShape.scale, static_cast<int8_t>(inShape.offset),
+                                  outShape.scale, static_cast<int8_t>(outShape.offset)),
+                    context->getInputBuffer<int8_t>(kInputTensor),
+                    context->getInputShape(kInputTensor),
+                    context->getOutputBuffer<int8_t>(kOutputTensor));
+        }
+        default:
+            NN_RET_CHECK_FAIL() << "Unsupported tensor type " << tensorType
+                                << " for operation RSQRT";
+    }
+}
+
 Result<Version> validate(const IOperationValidationContext* context) {
     NN_RET_CHECK_EQ(context->getNumInputs(), kNumInputs);
     NN_RET_CHECK_EQ(context->getNumOutputs(), kNumOutputs);
@@ -169,10 +237,6 @@
     return execute(context, std::log);
 }
 
-bool executeRsqrt(IOperationExecutionContext* context) {
-    return execute(context, [](float x) { return 1.f / std::sqrt(x); });
-}
-
 bool executeSin(IOperationExecutionContext* context) {
     return execute(context, std::sin);
 }
diff --git a/common/operations/Quantize.cpp b/common/operations/Quantize.cpp
index d1fcdbc..e2d0d96 100644
--- a/common/operations/Quantize.cpp
+++ b/common/operations/Quantize.cpp
@@ -36,6 +36,7 @@
 
 namespace {
 
+// The quantization formula also appears in Elementwise.cpp.
 template <typename T>
 bool quantizeToQuant8(const T* inputData, uint8_t* outputData, const Shape& outputShape) {
     NNTRACE_COMP("quantizeToQuant8");
@@ -48,6 +49,7 @@
     return true;
 }
 
+// The quantization formula also appears in Elementwise.cpp.
 template <typename T>
 bool quantizeToQuant8Signed(const T* inputData, int8_t* outputData, const Shape& outputShape) {
     NNTRACE_COMP("quantizeToQuant8Signed");
diff --git a/driver/sample_shim/android_arm/neuralnetworks_sample_sl_driver_prebuilt.so b/driver/sample_shim/android_arm/neuralnetworks_sample_sl_driver_prebuilt.so
index c9eed54..fe35fcc 100755
--- a/driver/sample_shim/android_arm/neuralnetworks_sample_sl_driver_prebuilt.so
+++ b/driver/sample_shim/android_arm/neuralnetworks_sample_sl_driver_prebuilt.so
Binary files differ
diff --git a/driver/sample_shim/android_arm64/neuralnetworks_sample_sl_driver_prebuilt.so b/driver/sample_shim/android_arm64/neuralnetworks_sample_sl_driver_prebuilt.so
index 30d5e9b..4961c73 100755
--- a/driver/sample_shim/android_arm64/neuralnetworks_sample_sl_driver_prebuilt.so
+++ b/driver/sample_shim/android_arm64/neuralnetworks_sample_sl_driver_prebuilt.so
Binary files differ
diff --git a/driver/sample_shim/android_x86/neuralnetworks_sample_sl_driver_prebuilt.so b/driver/sample_shim/android_x86/neuralnetworks_sample_sl_driver_prebuilt.so
index 4b31162..0466bb3 100755
--- a/driver/sample_shim/android_x86/neuralnetworks_sample_sl_driver_prebuilt.so
+++ b/driver/sample_shim/android_x86/neuralnetworks_sample_sl_driver_prebuilt.so
Binary files differ
diff --git a/driver/sample_shim/android_x86_64/neuralnetworks_sample_sl_driver_prebuilt.so b/driver/sample_shim/android_x86_64/neuralnetworks_sample_sl_driver_prebuilt.so
index 7245626..e05a6d0 100755
--- a/driver/sample_shim/android_x86_64/neuralnetworks_sample_sl_driver_prebuilt.so
+++ b/driver/sample_shim/android_x86_64/neuralnetworks_sample_sl_driver_prebuilt.so
Binary files differ
diff --git a/runtime/Android.bp b/runtime/Android.bp
index d460aa9..3ce7da6 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -209,6 +209,7 @@
     exclude_static_libs: [
         "libneuralnetworks_common",
         "neuralnetworks_types",
+        "server_configurable_flags",
     ],
     static_libs: [
         "libneuralnetworks_common_experimental",
@@ -240,6 +241,7 @@
         "ModelArgumentInfo.cpp",
         "ModelBuilder.cpp",
         "NeuralNetworks.cpp",
+        "ServerFlag.cpp",
         "SupportLibraryDiagnostic.cpp",
         "Telemetry.cpp",
         "TypeManager.cpp",
diff --git a/runtime/ExecutionPlan.cpp b/runtime/ExecutionPlan.cpp
index 5dffa3c..8d57e09 100644
--- a/runtime/ExecutionPlan.cpp
+++ b/runtime/ExecutionPlan.cpp
@@ -898,7 +898,8 @@
                                           executionPreference, priority);
             if (stepHasDynamicTemporaries) {
                 mHasDynamicTemporaries = true;
-                if (step->getDevice()->getFeatureLevel() < kHalVersionV1_2ToApi.featureLevel) {
+                if (!isCompliantVersion(kHalVersionV1_2ToApi.canonical,
+                                        step->getDevice()->getFeatureLevel())) {
                     // Until HAL 1.2, an Operand with lifetime SUBGRAPH_OUTPUT
                     // must have fully specified dimensions either in the
                     // Operand or in the RequestArgument.  In the case of a
diff --git a/runtime/FeatureLevel.h b/runtime/FeatureLevel.h
index edc6d66..bdeb778 100644
--- a/runtime/FeatureLevel.h
+++ b/runtime/FeatureLevel.h
@@ -19,21 +19,9 @@
 
 #include "NeuralNetworks.h"
 
-#ifdef NN_EXPERIMENTAL_FEATURE
-#include "NeuralNetworksExperimentalFeatures.h"
-#endif  // NN_EXPERIMENTAL_FEATURE
-
 namespace android {
 namespace nn {
 
-// TODO(b/201399117): Set this value based on feature level flag.
-// The current feature level of the NNAPI Runtime
-#ifdef NN_EXPERIMENTAL_FEATURE
-constexpr int64_t kCurrentNNAPIRuntimeFeatureLevel = ANEURALNETWORKS_FEATURE_LEVEL_EXPERIMENTAL;
-#else   // NN_EXPERIMENTAL_FEATURE
-constexpr int64_t kCurrentNNAPIRuntimeFeatureLevel = ANEURALNETWORKS_FEATURE_LEVEL_7;
-#endif  // NN_EXPERIMENTAL_FEATURE
-
 // The current version of the NNAPI APEX module.
 // Keep this value in sync with packages/modules/NeuralNetworks/apex/manifest.json.
 constexpr int64_t kNnapiApexVersion = 319999900;
diff --git a/runtime/Manager.cpp b/runtime/Manager.cpp
index b5bb992..1c78b4c 100644
--- a/runtime/Manager.cpp
+++ b/runtime/Manager.cpp
@@ -41,9 +41,9 @@
 #include <vector>
 
 #include "ExecutionCallback.h"
-#include "FeatureLevel.h"
 #include "Memory.h"
 #include "ModelArgumentInfo.h"
+#include "ServerFlag.h"
 #include "TypeManager.h"
 
 #ifndef NN_COMPATIBILITY_LIBRARY_BUILD
@@ -55,8 +55,39 @@
 #include "AppInfoFetcher.h"
 #endif  // NN_COMPATIBILITY_LIBRARY_BUILD
 
+#ifdef NN_EXPERIMENTAL_FEATURE
+#include "NeuralNetworksExperimentalFeatures.h"
+#endif  // NN_EXPERIMENTAL_FEATURE
+
 namespace android {
 namespace nn {
+namespace {
+
+Version getRuntimeFeatureLevelVersionHelper() {
+#if defined(NN_EXPERIMENTAL_FEATURE) && defined(NN_COMPATIBILITY_LIBRARY_BUILD)
+#error "NN_EXPERIMENTAL_FEATURE is not supported when NN_COMPATIBILITY_LIBRARY_BUILD is defined"
+#elif defined(NN_EXPERIMENTAL_FEATURE)
+    auto version = kVersionFeatureLevelExperimental;
+    // Enable "runtimeOnlyFeatures" to indicate that the runtime feature level version supports
+    // features that are only available in the runtime.
+    version.runtimeOnlyFeatures = true;
+#elif defined(NN_COMPATIBILITY_LIBRARY_BUILD)
+    auto version = serverFeatureLevelToVersion(kMaxFeatureLevelNum);
+#else   // !defined(NN_COMPATIBILITY_LIBRARY_BUILD) && !defined(NN_EXPERIMENTAL_FEATURE)
+    auto version = serverFeatureLevelToVersion(getServerFeatureLevelFlag());
+    // Enable "runtimeOnlyFeatures" to indicate that the runtime feature level version supports
+    // features that are only available in the runtime.
+    version.runtimeOnlyFeatures = true;
+#endif  // !defined(NN_COMPATIBILITY_LIBRARY_BUILD) && !defined(NN_EXPERIMENTAL_FEATURE)
+    return version;
+}
+
+Version getRuntimeFeatureLevelVersion() {
+    static const Version version = getRuntimeFeatureLevelVersionHelper();
+    return version;
+}
+
+}  // namespace
 
 // A Device with actual underlying driver
 class DriverDevice : public Device {
@@ -70,7 +101,7 @@
 
     const std::string& getName() const override { return kInterface->getName(); }
     const std::string& getVersionString() const override { return kInterface->getVersionString(); }
-    int64_t getFeatureLevel() const override;
+    Version getFeatureLevel() const override { return kInterface->getFeatureLevel(); }
     int32_t getType() const override { return static_cast<int32_t>(kInterface->getType()); }
     bool isUpdatable() const override { return kIsUpdatable; }
     const std::vector<Extension>& getSupportedExtensions() const override {
@@ -173,7 +204,7 @@
     }
 
     MemoryPreference getMemoryPreference() const override {
-        if (mDevice->getFeatureLevel() >= ANEURALNETWORKS_FEATURE_LEVEL_5) {
+        if (isCompliantVersion(kVersionFeatureLevel5, mDevice->getFeatureLevel())) {
             return {kDefaultRequestMemoryAlignment, kDefaultRequestMemoryPadding};
         } else {
             // We are not able to pass memory padding information to HIDL drivers, so return the
@@ -191,7 +222,7 @@
    public:
     DriverExecution(SharedExecution execution, Request request,
                     std::vector<const RuntimeMemory*> memories, MeasureTiming measure,
-                    OptionalDuration loopTimeoutDuration, int64_t deviceFeatureLevel)
+                    OptionalDuration loopTimeoutDuration, Version deviceFeatureLevel)
         : kExecution(std::move(execution)),
           kRequest(std::move(request)),
           kMemories(std::move(memories)),
@@ -219,7 +250,7 @@
     mutable std::map<const IBurst*, SharedExecution> mCachedBurstExecutions;
 
     // For fenced execution.
-    const int64_t kDeviceFeatureLevel;
+    const Version kDeviceFeatureLevel;
 };
 
 DriverDevice::DriverDevice(SharedDevice device, bool isUpdatable)
@@ -242,10 +273,8 @@
     return std::make_shared<DriverDevice>(std::move(device), isUpdatable);
 }
 
-int64_t DriverDevice::getFeatureLevel() const {
-    Version version = kInterface->getFeatureLevel();
-    CHECK(!version.runtimeOnlyFeatures);
-    switch (version.level) {
+int64_t DeviceManager::versionToFeatureLevel(Version::Level versionLevel) {
+    switch (versionLevel) {
         case Version::Level::FEATURE_LEVEL_1:
             return ANEURALNETWORKS_FEATURE_LEVEL_1;
         case Version::Level::FEATURE_LEVEL_2:
@@ -262,10 +291,10 @@
             return ANEURALNETWORKS_FEATURE_LEVEL_7;
 #ifdef NN_EXPERIMENTAL_FEATURE
         case Version::Level::FEATURE_LEVEL_EXPERIMENTAL:
-            break;
+            return ANEURALNETWORKS_FEATURE_LEVEL_EXPERIMENTAL;
 #endif  // NN_EXPERIMENTAL_FEATURE
     }
-    LOG(FATAL) << "Unsupported driver feature level: " << version;
+    LOG(FATAL) << "Unrecognized version " << versionLevel;
     return -1;
 }
 
@@ -605,7 +634,7 @@
     SyncFence syncFence = SyncFence::createAsSignaled();
     ExecuteFencedInfoCallback executeFencedInfoCallback = nullptr;
     Timing timing = {};
-    if (mDevice->getFeatureLevel() >= kHalVersionV1_3ToApi.featureLevel) {
+    if (isCompliantVersion(kHalVersionV1_3ToApi.canonical, mDevice->getFeatureLevel())) {
         auto result = mPreparedModel->executeFenced(request, waitForHandles, measure, deadline,
                                                     loopTimeoutDuration, timeoutDurationAfterFence);
         if (!result.ok()) {
@@ -743,7 +772,7 @@
     SyncFence syncFence = SyncFence::createAsSignaled();
     ExecuteFencedInfoCallback executeFencedInfoCallback = nullptr;
     Timing timing = {};
-    if (kDeviceFeatureLevel >= kHalVersionV1_3ToApi.featureLevel) {
+    if (isCompliantVersion(kHalVersionV1_3ToApi.canonical, kDeviceFeatureLevel)) {
         auto result =
                 kExecution->computeFenced(waitForHandles, deadline, timeoutDurationAfterFence);
         if (!result.ok()) {
@@ -838,7 +867,7 @@
 
     const std::string& getName() const override { return kName; }
     const std::string& getVersionString() const override { return kVersionString; }
-    int64_t getFeatureLevel() const override { return kFeatureLevel; }
+    Version getFeatureLevel() const override { return kVersion; }
     int32_t getType() const override { return ANEURALNETWORKS_DEVICE_CPU; }
     bool isUpdatable() const override { return false; }
     const std::vector<Extension>& getSupportedExtensions() const override {
@@ -873,7 +902,7 @@
 
    private:
     CpuDevice() = default;
-    const int64_t kFeatureLevel = kCurrentNNAPIRuntimeFeatureLevel;
+    const Version kVersion = getRuntimeFeatureLevelVersion();
     const std::string kName = "nnapi-reference";
 #ifndef NN_COMPATIBILITY_LIBRARY_BUILD
     const std::string kVersionString = build::GetBuildNumber();
@@ -979,6 +1008,17 @@
     return result;
 }
 
+template <typename Type>
+static Result<void> validateAndCheckCompliance(const Type& object) {
+    const auto version = NN_TRY(validate(object));
+    if (!isCompliantVersion(version, DeviceManager::get()->getRuntimeVersion())) {
+        return NN_ERROR() << "Object than is newer what is allowed. Version needed: " << version
+                          << ", current runtime version supported: "
+                          << DeviceManager::get()->getRuntimeVersion();
+    }
+    return {};
+}
+
 std::pair<int, std::shared_ptr<RuntimePreparedModel>> CpuDevice::prepareModel(
         const ModelFactory& makeModel, ExecutionPreference preference, Priority priority,
         const OptionalTimePoint& deadline, const CacheInfo& /*cacheInfo*/,
@@ -987,15 +1027,15 @@
             << "Should never call prepareModel with cache information on CpuDevice";
 
     const Model model = makeModel();
-    if (auto result = validate(model); !result.ok()) {
+    if (auto result = validateAndCheckCompliance(model); !result.ok()) {
         LOG(ERROR) << "Invalid Model: " << result.error();
         return {ANEURALNETWORKS_OP_FAILED, nullptr};
     }
-    if (auto result = validate(preference); !result.ok()) {
+    if (auto result = validateAndCheckCompliance(preference); !result.ok()) {
         LOG(ERROR) << "Invalid ExecutionPreference: " << result.error();
         return {ANEURALNETWORKS_OP_FAILED, nullptr};
     }
-    if (auto result = validate(priority); !result.ok()) {
+    if (auto result = validateAndCheckCompliance(priority); !result.ok()) {
         LOG(ERROR) << "Invalid Priority: " << result.error();
         return {ANEURALNETWORKS_OP_FAILED, nullptr};
     }
@@ -1219,6 +1259,10 @@
     return {result, -1, nullptr, timing};
 }
 
+int64_t DeviceManager::getRuntimeFeatureLevel() const {
+    return versionToFeatureLevel(mRuntimeVersion.level);
+}
+
 DeviceManager* DeviceManager::get() {
     static DeviceManager manager;
     return &manager;
@@ -1293,6 +1337,7 @@
 
 DeviceManager::DeviceManager() {
     VLOG(MANAGER) << "DeviceManager::DeviceManager";
+    mRuntimeVersion = getRuntimeFeatureLevelVersion();
     findAvailableDevices();
 #ifdef NN_DEBUGGABLE
     mStrictSlicing = (getProp("debug.nn.strict-slicing") != 0);
diff --git a/runtime/Manager.h b/runtime/Manager.h
index 2511e26..c92df8d 100644
--- a/runtime/Manager.h
+++ b/runtime/Manager.h
@@ -129,7 +129,7 @@
     // Introspection methods returning device information
     virtual const std::string& getName() const = 0;
     virtual const std::string& getVersionString() const = 0;
-    virtual int64_t getFeatureLevel() const = 0;
+    virtual Version getFeatureLevel() const = 0;
     virtual int32_t getType() const = 0;
     virtual bool isUpdatable() const = 0;
     virtual const std::vector<Extension>& getSupportedExtensions() const = 0;
@@ -169,6 +169,15 @@
         return mDevices;
     }
 
+    // Gets the runtime version corresponding to getServerFeatureLevelFlag (in ServerFlag.h).
+    Version getRuntimeVersion() const { return mRuntimeVersion; }
+
+    // Gets the runtime feature level corresponding to getServerFeatureLevelFlag (in ServerFlag.h).
+    int64_t getRuntimeFeatureLevel() const;
+
+    // Convert the internal Version level representation to the NDK representation.
+    static int64_t versionToFeatureLevel(Version::Level versionLevel);
+
     // For testing only:
     void setUseCpuOnly(bool useCpuOnly) { mSetCpuOnly = useCpuOnly; }
     bool getUseCpuOnly() const { return mSetCpuOnly; }
@@ -231,6 +240,9 @@
 
     void findAvailableDevices();
 
+    // Runtime version corresponding to getServerFeatureLevelFlag (in ServerFlag.h).
+    Version mRuntimeVersion;
+
     // List of all the devices we discovered (including CpuDevice).
     std::vector<std::shared_ptr<Device>> mDevices;
 
diff --git a/runtime/Memory.cpp b/runtime/Memory.cpp
index 01adf6d..02b6fb8 100644
--- a/runtime/Memory.cpp
+++ b/runtime/Memory.cpp
@@ -25,6 +25,7 @@
 #include <nnapi/SharedMemory.h>
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
+#include <nnapi/Validation.h>
 
 #include <algorithm>
 #include <memory>
@@ -465,7 +466,7 @@
     }
 #ifdef __ANDROID__
     mSupportsAhwb = std::all_of(devices.begin(), devices.end(), [](const auto* device) {
-        return device->getFeatureLevel() >= kHalVersionV1_3ToApi.featureLevel;
+        return isCompliantVersion(kHalVersionV1_3ToApi.canonical, device->getFeatureLevel());
     });
 #else   // __ANDROID__
     mSupportsAhwb = false;
diff --git a/runtime/ModelBuilder.cpp b/runtime/ModelBuilder.cpp
index 5ab182e..35f2f84 100644
--- a/runtime/ModelBuilder.cpp
+++ b/runtime/ModelBuilder.cpp
@@ -20,6 +20,7 @@
 
 #include <GraphDump.h>
 #include <LegacyUtils.h>
+#include <nnapi/Validation.h>
 
 #include <algorithm>
 #include <map>
@@ -535,8 +536,18 @@
     //       a CONSTANT_REFERENCE operand will not have correct .poolIndex, and
     //       validation will not work properly.
     const Model modelForValidation = makeModel();
-    if (auto result = validate(modelForValidation); !result.ok()) {
-        LOG(ERROR) << "ANeuralNetworksModel_finish called on invalid model: " << result.error();
+    const auto maybeVersion = validate(modelForValidation);
+    if (!maybeVersion.ok()) {
+        LOG(ERROR) << "ANeuralNetworksModel_finish called on invalid model: "
+                   << maybeVersion.error();
+        mInvalidModel = true;
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+    if (!isCompliantVersion(maybeVersion.value(), DeviceManager::get()->getRuntimeVersion())) {
+        LOG(ERROR) << "ANeuralNetworksModel_finish called on a model that is newer what is "
+                      "allowed. Model version needed: "
+                   << maybeVersion.value() << ", current runtime version supported: "
+                   << DeviceManager::get()->getRuntimeVersion();
         mInvalidModel = true;
         return ANEURALNETWORKS_BAD_DATA;
     }
diff --git a/runtime/NeuralNetworks.cpp b/runtime/NeuralNetworks.cpp
index 9bdf3a7..fe487dc 100644
--- a/runtime/NeuralNetworks.cpp
+++ b/runtime/NeuralNetworks.cpp
@@ -39,7 +39,6 @@
 #include "Event.h"
 #include "ExecutionBuilder.h"
 #include "ExecutionCallback.h"
-#include "FeatureLevel.h"
 #include "Manager.h"
 #include "Memory.h"
 #include "ModelBuilder.h"
@@ -722,7 +721,7 @@
         return ANEURALNETWORKS_UNEXPECTED_NULL;
     }
     Device* d = reinterpret_cast<Device*>(const_cast<ANeuralNetworksDevice*>(device));
-    int64_t dFeatureLevel = d->getFeatureLevel();
+    int64_t dFeatureLevel = DeviceManager::versionToFeatureLevel(d->getFeatureLevel().level);
     if (dFeatureLevel < 0) {
         return ANEURALNETWORKS_BAD_STATE;
     }
@@ -1651,7 +1650,7 @@
         return sRuntimeFeatureLevel;
     }
 #endif
-    return kCurrentNNAPIRuntimeFeatureLevel;
+    return DeviceManager::get()->getRuntimeFeatureLevel();
 }
 
 int ANeuralNetworksExecution_enableInputAndOutputPadding(ANeuralNetworksExecution* execution,
diff --git a/runtime/ServerFlag.cpp b/runtime/ServerFlag.cpp
index 0dbd369..17cac0c 100644
--- a/runtime/ServerFlag.cpp
+++ b/runtime/ServerFlag.cpp
@@ -16,36 +16,50 @@
 
 #define LOG_TAG "ServerFlag"
 
-#include <FlagUtils.h>
+#include "ServerFlag.h"
+
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
-#include <server_configurable_flags/get_flags.h>
+#include <nnapi/Types.h>
 #include <stdint.h>
 
 #include <string>
 
-namespace android {
-namespace nn {
+#if !defined(NN_COMPATIBILITY_LIBRARY_BUILD) && !defined(NN_EXPERIMENTAL_FEATURE)
+#include <server_configurable_flags/get_flags.h>
+#endif  // !defined(NN_COMPATIBILITY_LIBRARY_BUILD) && !defined(NN_EXPERIMENTAL_FEATURE)
 
-namespace {
-int64_t getServerFlagInt(std::string flagName, int64_t defaultValue, int64_t minValue,
-                         int64_t maxValue) {
-    int64_t flagValue = defaultValue;
-    if (!android::base::ParseInt(
-                server_configurable_flags::GetServerConfigurableFlag(
-                        std::string(kExprCategoryName), flagName, std::to_string(defaultValue)),
-                &flagValue, minValue, maxValue)) {
-        LOG(WARNING) << "Failed to parse flag " << flagName << " to int type. errno: " << errno;
+namespace android::nn {
+
+#if !defined(NN_COMPATIBILITY_LIBRARY_BUILD) && !defined(NN_EXPERIMENTAL_FEATURE)
+int64_t getServerFeatureLevelFlag() {
+    const std::string featureLevelString = server_configurable_flags::GetServerConfigurableFlag(
+            kExprCategoryName, kCurrentFeatureLevelFlagName,
+            std::to_string(kDefaultFeatureLevelNum));
+
+    int64_t featureLevel = kDefaultFeatureLevelNum;
+    const bool success = base::ParseInt(featureLevelString, &featureLevel, kMinFeatureLevelNum,
+                                        kMaxFeatureLevelNum);
+    if (!success) {
+        LOG(WARNING) << "Failed to parse result of GetServerConfigurableFlag, errno=" << errno;
     }
-    return flagValue;
+    return featureLevel;
+}
+#endif  // !defined(NN_COMPATIBILITY_LIBRARY_BUILD) && !defined(NN_EXPERIMENTAL_FEATURE)
+
+Version serverFeatureLevelToVersion(int64_t serverFeatureLevel) {
+    Version version;
+    switch (serverFeatureLevel) {
+        case 5:
+            return kVersionFeatureLevel5;
+        case 6:
+            return kVersionFeatureLevel6;
+        case 7:
+            return kVersionFeatureLevel7;
+        default:
+            LOG(FATAL) << "Invalid feature level flag value " << serverFeatureLevel;
+            return {};
+    }
 }
 
-}  // namespace
-
-int64_t _getServerFeatureLevelFlag() {
-    return getServerFlagInt(kCurrentFeatureLevelFlagName, kDefaultFeatureLevelNum,
-                            kMinFeatureLevelNum, kMaxFeatureLevelNum);
-}
-
-}  // namespace nn
-}  // namespace android
\ No newline at end of file
+}  // namespace android::nn
diff --git a/runtime/ServerFlag.h b/runtime/ServerFlag.h
new file mode 100644
index 0000000..fdc4f52
--- /dev/null
+++ b/runtime/ServerFlag.h
@@ -0,0 +1,49 @@
+/*
+ * 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_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_SERVER_FLAG_H
+#define ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_SERVER_FLAG_H
+
+#include <nnapi/Types.h>
+#include <stdint.h>
+
+#include "NeuralNetworks.h"
+
+namespace android::nn {
+
+// Keep these values consistent with server side configuration in
+// google3/googledata/experiments/mobile/android_platform/nnapi_native/features/feature_level.gcl.
+constexpr char kExprCategoryName[] = "nnapi_native";
+constexpr char kCurrentFeatureLevelFlagName[] = "current_feature_level";
+constexpr int64_t kDefaultFeatureLevelNum = 5;
+// When this value is updated, update kMinFeatureLevelCode in runtime/test/TestUpdatability.cpp with
+// the corresponding ANEURALNETWORKS_FEATURE_LEVEL_* version.
+constexpr int64_t kMinFeatureLevelNum = 5;
+constexpr int64_t kMaxFeatureLevelNum = 7;
+
+// Function to get server feature level flag. Note that this function should NOT be used directly.
+// Instead, clients are expected to use DeviceManager::getRuntimeVersion or
+// DeviceManager::getRuntimeFeatureLevel in runtime/Manager.h.
+#if !defined(NN_COMPATIBILITY_LIBRARY_BUILD) && !defined(NN_EXPERIMENTAL_FEATURE)
+int64_t getServerFeatureLevelFlag();
+#endif  // !defined(NN_COMPATIBILITY_LIBRARY_BUILD) && !defined(NN_EXPERIMENTAL_FEATURE)
+
+// Get the runtime version corresponding to the server feature flag value.
+Version serverFeatureLevelToVersion(int64_t serverFeatureLevel);
+
+}  // namespace android::nn
+
+#endif  // ANDROID_PACKAGES_MODULES_NEURALNETWORKS_RUNTIME_SERVER_FLAG_H
diff --git a/runtime/SupportLibraryDiagnostic.cpp b/runtime/SupportLibraryDiagnostic.cpp
index 372dabb..1ff03f7 100644
--- a/runtime/SupportLibraryDiagnostic.cpp
+++ b/runtime/SupportLibraryDiagnostic.cpp
@@ -20,7 +20,6 @@
 #include <utility>
 
 #include "ExecutionBuilder.h"
-#include "FeatureLevel.h"
 #include "NeuralNetworksSupportLibraryImpl.h"
 #include "Telemetry.h"
 
@@ -94,7 +93,7 @@
 
 int64_t SL_ANeuralNetworksDiagnosticCompilationInfo_getNnApiVersion(
         const ANeuralNetworksDiagnosticCompilationInfo* /*diagnosticCompilationInfo*/) {
-    return android::nn::kCurrentNNAPIRuntimeFeatureLevel;
+    return android::nn::DeviceManager::get()->getRuntimeFeatureLevel();
 }
 
 const uint8_t* SL_ANeuralNetworksDiagnosticCompilationInfo_getModelArchHash(
@@ -149,7 +148,7 @@
 
 int64_t SL_ANeuralNetworksDiagnosticExecutionInfo_getNnApiVersion(
         const ANeuralNetworksDiagnosticExecutionInfo* /*diagnosticExecutionInfo*/) {
-    return android::nn::kCurrentNNAPIRuntimeFeatureLevel;
+    return android::nn::DeviceManager::get()->getRuntimeFeatureLevel();
 }
 
 const uint8_t* SL_ANeuralNetworksDiagnosticExecutionInfo_getModelArchHash(
diff --git a/runtime/Telemetry.cpp b/runtime/Telemetry.cpp
index 698e7c5..0460918 100644
--- a/runtime/Telemetry.cpp
+++ b/runtime/Telemetry.cpp
@@ -25,7 +25,6 @@
 #include <utility>
 #include <vector>
 
-#include "FeatureLevel.h"
 #include "Manager.h"
 #include "NeuralNetworks.h"
 
diff --git a/runtime/test/Android.bp b/runtime/test/Android.bp
index 0205b18..c1425b8 100644
--- a/runtime/test/Android.bp
+++ b/runtime/test/Android.bp
@@ -58,6 +58,7 @@
         "libneuralnetworks_generated_test_harness",
         "libtextclassifier_hash_static",
         "neuralnetworks_utils_hal_service",
+        "server_configurable_flags",
     ],
     whole_static_libs: [
         "libcrypto_static",
@@ -124,6 +125,7 @@
     ],
     whole_static_libs: [
         "neuralnetworks_generated_AIDL_V2_example",
+        "neuralnetworks_generated_AIDL_V3_example",
         "neuralnetworks_generated_V1_0_example",
         "neuralnetworks_generated_V1_1_example",
         "neuralnetworks_generated_V1_2_example",
@@ -228,6 +230,7 @@
         "libneuralnetworks_common",
         "libneuralnetworks_static",
         "neuralnetworks_types",
+        "server_configurable_flags",
     ],
     static_libs: [
         "libneuralnetworks_common_experimental",
@@ -406,6 +409,7 @@
     ],
     whole_static_libs: [
         "neuralnetworks_generated_AIDL_V2_example",
+        "neuralnetworks_generated_AIDL_V3_example",
         "neuralnetworks_generated_V1_0_example",
         "neuralnetworks_generated_V1_1_example",
         "neuralnetworks_generated_V1_2_example",
@@ -565,6 +569,12 @@
 }
 
 cc_library_static {
+    name: "neuralnetworks_generated_AIDL_V3_example",
+    defaults: ["neuralnetworks_generated_defaults"],
+    srcs: ["generated/spec_AIDL_V3/*.example.cpp"],
+}
+
+cc_library_static {
     name: "neuralnetworks_generated_V1_3_cts_only_example",
     host_supported: true,
     defaults: ["neuralnetworks_float16"],
diff --git a/runtime/test/TestGenerated.cpp b/runtime/test/TestGenerated.cpp
index 5673923..63fa970 100644
--- a/runtime/test/TestGenerated.cpp
+++ b/runtime/test/TestGenerated.cpp
@@ -35,6 +35,8 @@
 
 #include "AndroidVersionUtil.h"
 #include "GeneratedTestUtils.h"
+#include "NeuralNetworks.h"
+#include "NeuralNetworksTypes.h"
 #include "TestHarness.h"
 #include "TestNeuralNetworksWrapper.h"
 #include "TestUtils.h"
@@ -401,6 +403,41 @@
     }
 }
 
+static int64_t getRuntimeFeatureLevel() {
+    if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) {
+        return ANeuralNetworks_getRuntimeFeatureLevel();
+    }
+#if defined(__BIONIC__)
+    return android_get_device_api_level();
+#else
+    return __ANDROID_API__;
+#endif  // __BIONIC__
+}
+
+static std::optional<int64_t> halVersionToFeatureLevel(TestHalVersion halVersion) {
+    switch (halVersion) {
+        case TestHalVersion::UNKNOWN:
+            return std::nullopt;
+        case TestHalVersion::V1_0:
+            return ANEURALNETWORKS_FEATURE_LEVEL_1;
+        case TestHalVersion::V1_1:
+            return ANEURALNETWORKS_FEATURE_LEVEL_2;
+        case TestHalVersion::V1_2:
+            return ANEURALNETWORKS_FEATURE_LEVEL_3;
+        case TestHalVersion::V1_3:
+            return ANEURALNETWORKS_FEATURE_LEVEL_4;
+        case TestHalVersion::AIDL_V1:
+            return ANEURALNETWORKS_FEATURE_LEVEL_5;
+        case TestHalVersion::AIDL_V2:
+            return ANEURALNETWORKS_FEATURE_LEVEL_6;
+        case TestHalVersion::AIDL_V3:
+            return ANEURALNETWORKS_FEATURE_LEVEL_7;
+    }
+    LOG(FATAL) << "Unrecognized TestHalVersion "
+               << static_cast<std::underlying_type_t<TestHalVersion>>(halVersion);
+    return std::nullopt;
+}
+
 bool GeneratedTests::shouldSkipTest() {
     // A map of {min VNDK version -> tests that should be skipped with earlier VNDK versions}.
     // The listed tests are added in a later release, but exercising old APIs. They should be
@@ -418,6 +455,13 @@
             return true;
         }
     }
+
+    // Skip test cases that are newer than what is allowed by
+    // ANeuralNetworks_getRuntimeFeatureLevel.
+    if (const auto featureLevelNeeded = halVersionToFeatureLevel(testModel.minSupportedVersion)) {
+        return featureLevelNeeded.value() > getRuntimeFeatureLevel();
+    }
+
     return false;
 }
 
diff --git a/runtime/test/TestPartitioningRandom.cpp b/runtime/test/TestPartitioningRandom.cpp
index 73ab165..225ffdd 100644
--- a/runtime/test/TestPartitioningRandom.cpp
+++ b/runtime/test/TestPartitioningRandom.cpp
@@ -1122,7 +1122,8 @@
             compilationResult == Result::OP_FAILED && hasUnknownDimensions &&
             cNoFallback.getExecutionPlan().hasDynamicTemporaries() &&
             std::any_of(devices.begin(), devices.end(), [](const std::shared_ptr<Device>& device) {
-                return device->getFeatureLevel() < nn::kHalVersionV1_2ToApi.featureLevel;
+                return !isCompliantVersion(nn::kHalVersionV1_2ToApi.canonical,
+                                           device->getFeatureLevel());
             });
     const bool fallbackNeededForStepModelWithNoInputsOrNoOutputs =
             cNoFallback.getExecutionPlan().forTest_hasStepModelWithNoInputsOrNoOutputs();
diff --git a/runtime/test/TestUpdatability.cpp b/runtime/test/TestUpdatability.cpp
index bd5b517..1825b3e 100644
--- a/runtime/test/TestUpdatability.cpp
+++ b/runtime/test/TestUpdatability.cpp
@@ -20,9 +20,15 @@
 
 class UpdatabilityTest : public ::testing::Test {};
 
+// Keep this value in sync with the corresponding value of kMinFeatureLevelNum in
+// runtime/ServerFlag.h.
+constexpr int64_t kMinFeatureLevelCode = ANEURALNETWORKS_FEATURE_LEVEL_5;
+
 TEST_F(UpdatabilityTest, GetFeatureLevel) {
     if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) {
-        EXPECT_GE(ANeuralNetworks_getRuntimeFeatureLevel(), ANEURALNETWORKS_FEATURE_LEVEL_5);
+        // Ensure that the feature level returned is never less than what is allowed by the server
+        // feature level flag.
+        EXPECT_GE(ANeuralNetworks_getRuntimeFeatureLevel(), kMinFeatureLevelCode);
     } else {
         GTEST_SKIP();
     }
diff --git a/runtime/test/TestValidateOperations.cpp b/runtime/test/TestValidateOperations.cpp
index 61d11a9..44d2382 100644
--- a/runtime/test/TestValidateOperations.cpp
+++ b/runtime/test/TestValidateOperations.cpp
@@ -1025,6 +1025,16 @@
             });
 }
 
+// Test quantization parameters that are inconsistent among operands.
+enum class BadQuantization { NONE, zeroPoint, scale };
+void scramble(ANeuralNetworksOperandType* type, BadQuantization bad) {
+    if (bad == BadQuantization::zeroPoint) {
+        type->zeroPoint = 1;
+    } else if (bad == BadQuantization::scale) {
+        type->scale *= 2;
+    }
+};
+
 void argMinMaxTest(ANeuralNetworksOperationType operationCode, int32_t inputOperandType) {
     SCOPED_TRACE(inputOperandType);
     uint32_t inputDimensions[4] = {2, 2, 2, 2};
@@ -1498,6 +1508,14 @@
     activationOpTest(ANEURALNETWORKS_RSQRT, ANEURALNETWORKS_TENSOR_FLOAT32);
 }
 
+TEST(OperationValidationTest, RSQRT_quant8) {
+    activationOpTest(ANEURALNETWORKS_RSQRT, ANEURALNETWORKS_TENSOR_QUANT8_ASYMM);
+}
+
+TEST(OperationValidationTest, RSQRT_quant8_signed) {
+    activationOpTest(ANEURALNETWORKS_RSQRT, ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED);
+}
+
 TEST(OperationValidationTest, SIN_float16) {
     activationOpTest(ANEURALNETWORKS_SIN, ANEURALNETWORKS_TENSOR_FLOAT16);
 }
@@ -1741,25 +1759,44 @@
     meanOpTest(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED);
 }
 
-void padOpTest(int32_t inputOperandCode) {
+void padOpTest(ANeuralNetworksOperationType operationCode, int32_t inputOperandCode) {
     SCOPED_TRACE(inputOperandCode);
+
     uint32_t inputDimensions[4] = {2, 2, 2, 2};
     ANeuralNetworksOperandType input = getOpType(inputOperandCode, 4, inputDimensions);
-    uint32_t padSizeDimensions[1] = {4};
+    uint32_t padSizeDimensions[2] = {4, 2};
     ANeuralNetworksOperandType padSize =
-            getOpType(ANEURALNETWORKS_TENSOR_INT32, 1, padSizeDimensions);
+            getOpType(ANEURALNETWORKS_TENSOR_INT32, 2, padSizeDimensions);
+    std::vector<ANeuralNetworksOperandType> inputs = {input, padSize};
+    if (operationCode == ANEURALNETWORKS_MIRROR_PAD) {
+        inputs.push_back(getOpType(ANEURALNETWORKS_INT32));
+    }
+
     uint32_t outputDimensions[4] = {4, 3, 4, 3};
     ANeuralNetworksOperandType output = getOpType(inputOperandCode, 4, outputDimensions);
-    OperationTestBase test(ANEURALNETWORKS_PAD, {input, padSize}, {output},
-                           {{TensorRankConstraint::UpTo(4)}});
+
+    std::vector<TensorRankMutator> inputRankMutators;
+    if (operationCode == ANEURALNETWORKS_PAD) {
+        inputRankMutators.push_back({TensorRankConstraint::UpTo(4)});
+    }
+
+    OperationTestBase test(operationCode, inputs, {output}, inputRankMutators);
     test.testOpsValidations();
 }
 
 TEST(OperationValidationTest, PAD) {
-    padOpTest(ANEURALNETWORKS_TENSOR_FLOAT16);
-    padOpTest(ANEURALNETWORKS_TENSOR_FLOAT32);
-    padOpTest(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM);
-    padOpTest(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED);
+    padOpTest(ANEURALNETWORKS_PAD, ANEURALNETWORKS_TENSOR_FLOAT16);
+    padOpTest(ANEURALNETWORKS_PAD, ANEURALNETWORKS_TENSOR_FLOAT32);
+    padOpTest(ANEURALNETWORKS_PAD, ANEURALNETWORKS_TENSOR_QUANT8_ASYMM);
+    padOpTest(ANEURALNETWORKS_PAD, ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED);
+}
+
+TEST(OperationValidationTest, MIRROR_PAD) {
+    padOpTest(ANEURALNETWORKS_MIRROR_PAD, ANEURALNETWORKS_TENSOR_FLOAT16);
+    padOpTest(ANEURALNETWORKS_MIRROR_PAD, ANEURALNETWORKS_TENSOR_FLOAT32);
+    padOpTest(ANEURALNETWORKS_MIRROR_PAD, ANEURALNETWORKS_TENSOR_QUANT8_ASYMM);
+    padOpTest(ANEURALNETWORKS_MIRROR_PAD, ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED);
+    padOpTest(ANEURALNETWORKS_MIRROR_PAD, ANEURALNETWORKS_TENSOR_INT32);
 }
 
 void padV2OpTest(int32_t inputOperandCode) {
@@ -4734,16 +4771,7 @@
 }
 
 // Test quantization parameters that are inconsistent among operands.
-enum class PackBadQuantization { NONE, zeroPoint, scale };
-void packTestBadQuantization(int32_t operandCode, PackBadQuantization bad) {
-    auto scramble = [bad](ANeuralNetworksOperandType* type) {
-        if (bad == PackBadQuantization::zeroPoint) {
-            type->zeroPoint = 1;
-        } else if (bad == PackBadQuantization::scale) {
-            type->scale *= 2;
-        }
-    };
-
+void packTestBadQuantization(int32_t operandCode, BadQuantization bad) {
     constexpr uint32_t inputTensorCount = 2;
     const uint32_t inputDimensions[3] = {4, 5, 6};
     constexpr size_t inputRank = sizeof(inputDimensions) / sizeof(inputDimensions[0]);
@@ -4767,12 +4795,12 @@
         ANeuralNetworksOperandType outputType =
                 getOpType(operandCode, outputRank, outputDimensions);
         if (deviant == inputTensorCount) {
-            scramble(&outputType);
+            scramble(&outputType, bad);
         } else {
-            scramble(&inputTypes[1 + deviant]);
+            scramble(&inputTypes[1 + deviant], bad);
         }
         OperationTestBase packTest(ANEURALNETWORKS_PACK, inputTypes, {outputType});
-        if (bad == PackBadQuantization::NONE) {
+        if (bad == BadQuantization::NONE) {
             packTest.testSuccess();
             return;
         } else {
@@ -4784,24 +4812,23 @@
 TEST(OperationValidationTest, PACK_quant8_bad_none) {
     // Make sure packTestBadQuantization starts with a valid operation and only corrupts what it
     // intends to.
-    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, PackBadQuantization::NONE);
+    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, BadQuantization::NONE);
 }
 
 TEST(OperationValidationTest, PACK_quant8_bad_zeroPoint) {
-    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, PackBadQuantization::zeroPoint);
+    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, BadQuantization::zeroPoint);
 }
 
 TEST(OperationValidationTest, PACK_quant8_bad_scale) {
-    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, PackBadQuantization::scale);
+    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, BadQuantization::scale);
 }
 
 TEST(OperationValidationTest, PACK_quant8_signed_bad_zeroPoint) {
-    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED,
-                            PackBadQuantization::zeroPoint);
+    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED, BadQuantization::zeroPoint);
 }
 
 TEST(OperationValidationTest, PACK_quant8_signed_bad_scale) {
-    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED, PackBadQuantization::scale);
+    packTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED, BadQuantization::scale);
 }
 
 // Test ranks that are inconsistent among operands.
@@ -4880,4 +4907,160 @@
     packTestBadRank(ANEURALNETWORKS_TENSOR_FLOAT32, 1);
 }
 
+void reverseTest(int32_t operandCode) {
+    const uint32_t tensorDimensions[3] = {4, 5, 6};
+    constexpr size_t tensorRank = sizeof(tensorDimensions) / sizeof(tensorDimensions[0]);
+    const ANeuralNetworksOperandType tensorType =
+            getOpType(operandCode, tensorRank, tensorDimensions);
+
+    const uint32_t axisDimensions[1] = {0};
+    constexpr size_t axisRank = sizeof(axisDimensions) / sizeof(axisDimensions[0]);
+    const ANeuralNetworksOperandType axisType =
+            getOpType(ANEURALNETWORKS_TENSOR_INT32, axisRank, axisDimensions);
+
+    OperationTestBase reverseTest(ANEURALNETWORKS_REVERSE, {tensorType, axisType}, {tensorType});
+    reverseTest.testOpsValidations();
+}
+
+TEST(OperationValidationTest, REVERSE_float16) {
+    reverseTest(ANEURALNETWORKS_TENSOR_FLOAT16);
+}
+
+TEST(OperationValidationTest, REVERSE_float32) {
+    reverseTest(ANEURALNETWORKS_TENSOR_FLOAT32);
+}
+
+TEST(OperationValidationTest, REVERSE_quant8) {
+    reverseTest(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM);
+}
+
+TEST(OperationValidationTest, REVERSE_quant8_signed) {
+    reverseTest(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED);
+}
+
+TEST(OperationValidationTest, REVERSE_int32) {
+    reverseTest(ANEURALNETWORKS_TENSOR_INT32);
+}
+
+// Test quantization parameters that are inconsistent among operands.
+void reverseTestBadQuantization(int32_t operandCode, BadQuantization bad) {
+    const uint32_t tensorDimensions[3] = {4, 5, 6};
+    constexpr size_t tensorRank = sizeof(tensorDimensions) / sizeof(tensorDimensions[0]);
+    const ANeuralNetworksOperandType tensorType =
+            getOpType(operandCode, tensorRank, tensorDimensions);
+
+    const uint32_t axisDimensions[1] = {0};
+    constexpr size_t axisRank = sizeof(axisDimensions) / sizeof(axisDimensions[0]);
+    const ANeuralNetworksOperandType axisType =
+            getOpType(ANEURALNETWORKS_TENSOR_INT32, axisRank, axisDimensions);
+
+    ANeuralNetworksOperandType outputType = tensorType;
+    scramble(&outputType, bad);
+
+    OperationTestBase reverseTest(ANEURALNETWORKS_REVERSE, {tensorType, axisType}, {outputType});
+    if (bad == BadQuantization::NONE) {
+        reverseTest.testSuccess();
+        return;
+    } else {
+        reverseTest.testFailure(ANEURALNETWORKS_BAD_DATA);
+    }
+}
+
+TEST(OperationValidationTest, REVERSE_quant8_bad_none) {
+    // Make sure reverseTestBadQuantization starts with a valid operation and only corrupts what it
+    // intends to.
+    reverseTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, BadQuantization::NONE);
+}
+
+TEST(OperationValidationTest, REVERSE_quant8_bad_zeroPoint) {
+    reverseTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, BadQuantization::zeroPoint);
+}
+
+TEST(OperationValidationTest, REVERSE_quant8_bad_scale) {
+    reverseTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, BadQuantization::scale);
+}
+
+TEST(OperationValidationTest, REVERSE_quant8_signed_bad_zeroPoint) {
+    reverseTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED,
+                               BadQuantization::zeroPoint);
+}
+
+TEST(OperationValidationTest, REVERSE_quant8_signed_bad_scale) {
+    reverseTestBadQuantization(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED, BadQuantization::scale);
+}
+
+// Test ranks that are inconsistent among operands or otherwise incorrect.
+void reverseTestBadRank(uint32_t operandCode, int adjustRank) {
+    const uint32_t tensorDimensions[3] = {4, 5, 6};
+    constexpr size_t tensorRank = sizeof(tensorDimensions) / sizeof(tensorDimensions[0]);
+    const ANeuralNetworksOperandType tensorType =
+            getOpType(operandCode, tensorRank, tensorDimensions);
+
+    const uint32_t axisDimensions[1] = {0};
+    constexpr size_t axisRank = sizeof(axisDimensions) / sizeof(axisDimensions[0]);
+    const ANeuralNetworksOperandType axisType =
+            getOpType(ANEURALNETWORKS_TENSOR_INT32, axisRank, axisDimensions);
+
+    constexpr size_t kOperandCount = 3;  // 2 inputs, 1 output
+
+    // The "deviant" is the operand whose rank is to be changed.
+    for (uint32_t deviant = 0; deviant < kOperandCount; ++deviant) {
+        SCOPED_TRACE(deviant);
+
+        // input 0, input 1, output 0
+        std::vector<ANeuralNetworksOperandType> operands = {tensorType, axisType, tensorType};
+        ASSERT_EQ(operands.size(), kOperandCount);
+
+        std::vector<uint32_t> scrambledDimensions;
+        auto scramble = [adjustRank,
+                         &scrambledDimensions](ANeuralNetworksOperandType* type) -> bool {
+            if (!adjustRank) {
+                return true;
+            }
+            if (adjustRank < 0) {
+                if (type->dimensionCount <= uint32_t(-adjustRank)) {
+                    // not a valid test scenario
+                    return false;
+                }
+                type->dimensionCount += adjustRank;
+                return true;
+            }
+            const uint32_t oldRank = type->dimensionCount;
+            const uint32_t newRank = oldRank + adjustRank;
+            EXPECT_EQ(scrambledDimensions.size(), size_t(0));  // only use this vector once
+            scrambledDimensions.assign(&type->dimensions[0], &type->dimensions[oldRank]);
+            scrambledDimensions.resize(newRank, /* arbitrary choice */ 7);
+            type->dimensionCount = newRank;
+            type->dimensions = &scrambledDimensions[0];
+            return true;
+        };
+
+        if (!scramble(&operands[deviant])) {
+            continue;
+        }
+        OperationTestBase reverseTest(ANEURALNETWORKS_REVERSE, {operands[0], operands[1]},
+                                      {operands[2]});
+        if (adjustRank) {
+            reverseTest.testFailure(ANEURALNETWORKS_BAD_DATA);
+        } else {
+            reverseTest.testSuccess();
+            return;
+        }
+    }
+}
+
+TEST(OperationValidationTest, REVERSE_float32_rank_good) {
+    // Make sure reverseTestBadRank starts with a valid operation and only corrupts it when it
+    // intends to.
+    reverseTestBadRank(ANEURALNETWORKS_TENSOR_FLOAT32, 0);
+}
+
+TEST(OperationValidationTest, REVERSE_float32_rank_lo) {
+    reverseTestBadRank(ANEURALNETWORKS_TENSOR_FLOAT32, -1);
+}
+
+TEST(OperationValidationTest, REVERSE_float32_rank_hi) {
+    reverseTestBadRank(ANEURALNETWORKS_TENSOR_FLOAT32, 1);
+}
+
 }  // end namespace
diff --git a/runtime/test/generated/spec_AIDL_V3/rsqrt_quant8.example.cpp b/runtime/test/generated/spec_AIDL_V3/rsqrt_quant8.example.cpp
new file mode 100644
index 0000000..e8828ab
--- /dev/null
+++ b/runtime/test/generated/spec_AIDL_V3/rsqrt_quant8.example.cpp
@@ -0,0 +1,518 @@
+// Generated from rsqrt_quant8.mod.py
+// DO NOT EDIT
+// clang-format off
+#include "TestHarness.h"
+using namespace test_helper;
+
+namespace generated_tests::rsqrt_quant8 {
+
+const TestModel& get_test_model_25h_0_25h_0() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // input0
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({1, 4, 16, 64}),
+                            .dimensions = {4},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // output0
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({8, 4, 2, 1}),
+                            .dimensions = {4},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {0},
+                            .outputs = {1},
+                            .type = TestOperationType::RSQRT
+                        }},
+                .outputIndexes = {1}
+            },
+        .minSupportedVersion = TestHalVersion::AIDL_V3,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_25h_0_25h_0 = TestModelManager::get().add("rsqrt_quant8_25h_0_25h_0", get_test_model_25h_0_25h_0());
+
+}  // namespace generated_tests::rsqrt_quant8
+
+namespace generated_tests::rsqrt_quant8 {
+
+const TestModel& get_test_model_25h_0_25h_0_all_inputs_as_internal() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {2},
+                .operands = {{ // input0
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({}),
+                            .dimensions = {4},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::TEMPORARY_VARIABLE,
+                            .numberOfConsumers = 1,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // output0
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({8, 4, 2, 1}),
+                            .dimensions = {4},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // input0_new
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({1, 4, 16, 64}),
+                            .dimensions = {4},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // placeholder
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({0}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // param
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::INT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {2, 3, 4},
+                            .outputs = {0},
+                            .type = TestOperationType::ADD
+                        }, {
+                            .inputs = {0},
+                            .outputs = {1},
+                            .type = TestOperationType::RSQRT
+                        }},
+                .outputIndexes = {1}
+            },
+        .minSupportedVersion = TestHalVersion::AIDL_V3,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_25h_0_25h_0_all_inputs_as_internal = TestModelManager::get().add("rsqrt_quant8_25h_0_25h_0_all_inputs_as_internal", get_test_model_25h_0_25h_0_all_inputs_as_internal());
+
+}  // namespace generated_tests::rsqrt_quant8
+
+namespace generated_tests::rsqrt_quant8 {
+
+const TestModel& get_test_model_25h_0_1h_75() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // input01
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({16, 64}),
+                            .dimensions = {2},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // output01
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({125, 100}),
+                            .dimensions = {2},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.01f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 75
+                        }},
+                .operations = {{
+                            .inputs = {0},
+                            .outputs = {1},
+                            .type = TestOperationType::RSQRT
+                        }},
+                .outputIndexes = {1}
+            },
+        .minSupportedVersion = TestHalVersion::AIDL_V3,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_25h_0_1h_75 = TestModelManager::get().add("rsqrt_quant8_25h_0_1h_75", get_test_model_25h_0_1h_75());
+
+}  // namespace generated_tests::rsqrt_quant8
+
+namespace generated_tests::rsqrt_quant8 {
+
+const TestModel& get_test_model_25h_0_1h_75_all_inputs_as_internal() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {2},
+                .operands = {{ // input01
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({}),
+                            .dimensions = {2},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::TEMPORARY_VARIABLE,
+                            .numberOfConsumers = 1,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // output01
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({125, 100}),
+                            .dimensions = {2},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.01f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 75
+                        }, { // input01_new
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({16, 64}),
+                            .dimensions = {2},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // placeholder1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({0}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // param1
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::INT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {2, 3, 4},
+                            .outputs = {0},
+                            .type = TestOperationType::ADD
+                        }, {
+                            .inputs = {0},
+                            .outputs = {1},
+                            .type = TestOperationType::RSQRT
+                        }},
+                .outputIndexes = {1}
+            },
+        .minSupportedVersion = TestHalVersion::AIDL_V3,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_25h_0_1h_75_all_inputs_as_internal = TestModelManager::get().add("rsqrt_quant8_25h_0_1h_75_all_inputs_as_internal", get_test_model_25h_0_1h_75_all_inputs_as_internal());
+
+}  // namespace generated_tests::rsqrt_quant8
+
+namespace generated_tests::rsqrt_quant8 {
+
+const TestModel& get_test_model_125t_10_25h_0() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // input02
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({12, 18, 42}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.125f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 10
+                        }, { // output02
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({8, 4, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {0},
+                            .outputs = {1},
+                            .type = TestOperationType::RSQRT
+                        }},
+                .outputIndexes = {1}
+            },
+        .minSupportedVersion = TestHalVersion::AIDL_V3,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_125t_10_25h_0 = TestModelManager::get().add("rsqrt_quant8_125t_10_25h_0", get_test_model_125t_10_25h_0());
+
+}  // namespace generated_tests::rsqrt_quant8
+
+namespace generated_tests::rsqrt_quant8 {
+
+const TestModel& get_test_model_125t_10_25h_0_all_inputs_as_internal() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {2},
+                .operands = {{ // input02
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::TEMPORARY_VARIABLE,
+                            .numberOfConsumers = 1,
+                            .scale = 0.125f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 10
+                        }, { // output02
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({8, 4, 2}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.25f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 0
+                        }, { // input02_new
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({12, 18, 42}),
+                            .dimensions = {3},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.125f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 10
+                        }, { // placeholder2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({10}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.125f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 10
+                        }, { // param2
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::INT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {2, 3, 4},
+                            .outputs = {0},
+                            .type = TestOperationType::ADD
+                        }, {
+                            .inputs = {0},
+                            .outputs = {1},
+                            .type = TestOperationType::RSQRT
+                        }},
+                .outputIndexes = {1}
+            },
+        .minSupportedVersion = TestHalVersion::AIDL_V3,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_125t_10_25h_0_all_inputs_as_internal = TestModelManager::get().add("rsqrt_quant8_125t_10_25h_0_all_inputs_as_internal", get_test_model_125t_10_25h_0_all_inputs_as_internal());
+
+}  // namespace generated_tests::rsqrt_quant8
+
+namespace generated_tests::rsqrt_quant8 {
+
+const TestModel& get_test_model_125t_10_1h_75() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {0},
+                .operands = {{ // input03
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({42}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.125f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 10
+                        }, { // output03
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({125}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.01f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 75
+                        }},
+                .operations = {{
+                            .inputs = {0},
+                            .outputs = {1},
+                            .type = TestOperationType::RSQRT
+                        }},
+                .outputIndexes = {1}
+            },
+        .minSupportedVersion = TestHalVersion::AIDL_V3,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_125t_10_1h_75 = TestModelManager::get().add("rsqrt_quant8_125t_10_1h_75", get_test_model_125t_10_1h_75());
+
+}  // namespace generated_tests::rsqrt_quant8
+
+namespace generated_tests::rsqrt_quant8 {
+
+const TestModel& get_test_model_125t_10_1h_75_all_inputs_as_internal() {
+    static TestModel model = {
+        .expectFailure = false,
+        .expectedMultinomialDistributionTolerance = 0,
+        .isRelaxed = false,
+        .main = {
+                .inputIndexes = {2},
+                .operands = {{ // input03
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::TEMPORARY_VARIABLE,
+                            .numberOfConsumers = 1,
+                            .scale = 0.125f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 10
+                        }, { // output03
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({125}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT,
+                            .numberOfConsumers = 0,
+                            .scale = 0.01f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 75
+                        }, { // input03_new
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({42}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+                            .numberOfConsumers = 1,
+                            .scale = 0.125f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 10
+                        }, { // placeholder3
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<uint8_t>({10}),
+                            .dimensions = {1},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.125f,
+                            .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+                            .zeroPoint = 10
+                        }, { // param3
+                            .channelQuant = {},
+                            .data = TestBuffer::createFromVector<int32_t>({0}),
+                            .dimensions = {},
+                            .isIgnored = false,
+                            .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+                            .numberOfConsumers = 1,
+                            .scale = 0.0f,
+                            .type = TestOperandType::INT32,
+                            .zeroPoint = 0
+                        }},
+                .operations = {{
+                            .inputs = {2, 3, 4},
+                            .outputs = {0},
+                            .type = TestOperationType::ADD
+                        }, {
+                            .inputs = {0},
+                            .outputs = {1},
+                            .type = TestOperationType::RSQRT
+                        }},
+                .outputIndexes = {1}
+            },
+        .minSupportedVersion = TestHalVersion::AIDL_V3,
+        .referenced = {}
+    };
+    return model;
+}
+
+const auto dummy_test_model_125t_10_1h_75_all_inputs_as_internal = TestModelManager::get().add("rsqrt_quant8_125t_10_1h_75_all_inputs_as_internal", get_test_model_125t_10_1h_75_all_inputs_as_internal());
+
+}  // namespace generated_tests::rsqrt_quant8
+
diff --git a/runtime/test/specs/AIDL_V3/rsqrt_quant8.mod.py b/runtime/test/specs/AIDL_V3/rsqrt_quant8.mod.py
new file mode 100644
index 0000000..edbc335
--- /dev/null
+++ b/runtime/test/specs/AIDL_V3/rsqrt_quant8.mod.py
@@ -0,0 +1,52 @@
+#
+# 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.
+#
+
+# value = (8_bit_encoding - zeroPoint) * scale
+
+# If square roots are 0.5, 1, 2, 4
+# Then reciprocal square roots (outputs) are 2, 1, 0.5, 0.25
+# And squares (inputs) are 0.25, 1, 4, 16
+
+for inScale, inOffset, inToken in [(0.25, 0, "25h_0"),
+                                   (0.125, 10, "125t_10")]:
+    for outScale, outOffset, outToken  in [(0.25, 0, "25h_0"),
+                                            (0.01, 75, "1h_75")]:
+
+        input0_values = []
+        output0_values = []
+        for in0, out0 in [(0.25, 2),
+                          (1, 1),
+                          (4, 0.5),
+                          (16, 0.25)]:
+            input0_value = in0 / inScale + inOffset
+            output0_value = out0 / outScale + outOffset
+            if 0 <= input0_value < 128 and 0 <= output0_value < 128:
+                # We use [0, 128) as the range because the same values are used for
+                # both TENSOR_QUANT8_ASYMM and TENSOR_QUANT8_ASYMM_SIGNED  testing
+                input0_values.append(input0_value)
+                output0_values.append(output0_value)
+
+        input0 = Input("input0", "TENSOR_QUANT8_ASYMM", "{%d}, %f, %d" % (len(input0_values), inScale, inOffset))
+        output0 = Output("output0", "TENSOR_QUANT8_ASYMM", "{%d}, %f, %d" % (len(output0_values), outScale, outOffset))
+        model = Model().Operation("RSQRT", input0).To(output0)
+
+        example_name = "%s_%s" % (inToken, outToken)
+        Example({
+            input0: input0_values,
+            output0: output0_values,
+        }, name=example_name)
+
+# We rely on QuantizationCouplingTest to replicate this test case for TENSOR_QUANT8_ASYMM_SIGNED.
diff --git a/runtime/test/specs/generate_all_tests.sh b/runtime/test/specs/generate_all_tests.sh
index a518abc..832131a 100755
--- a/runtime/test/specs/generate_all_tests.sh
+++ b/runtime/test/specs/generate_all_tests.sh
@@ -17,7 +17,7 @@
 set -Eeuox pipefail
 cd "$(dirname "$0")/.."  # runtime/test
 
-NNAPI_VERSIONS="V1_0 V1_1 V1_2 V1_3 V1_3_cts_only AIDL_V2 experimental"
+NNAPI_VERSIONS="V1_0 V1_1 V1_2 V1_3 V1_3_cts_only AIDL_V2 AIDL_V3 experimental"
 EXAMPLE_GENERATOR="../../tools/test_generator/example_generator.py"
 
 for source_version in $NNAPI_VERSIONS; do