# Test KleidiCV and verify SME backend support

## In this learning path

- [Introduction](https://learn.arm.com/learning-paths/laptops-and-desktops/kleidicv-on-mac/)
- [Download and build KleidiCV software](https://learn.arm.com/learning-paths/laptops-and-desktops/kleidicv-on-mac/build-1/)
- [Test KleidiCV and verify SME backend support](https://learn.arm.com/learning-paths/laptops-and-desktops/kleidicv-on-mac/run-test-2/)
- [Next Steps](https://learn.arm.com/learning-paths/laptops-and-desktops/kleidicv-on-mac/_next-steps/)

## Run the test
Once the build steps are complete, you can run the KleidiCV and OpenCV tests. The KleidiCV API test checks the public C++ API and confirms that the build is working as expected. To run the test, use the following command:

```bash
./build-kleidicv-benchmark-SME/test/api/kleidicv-api-test
```

You will see output showing the number of tests run and their results. The full test log is omitted here for clarity.

```bash
./build-kleidicv-benchmark-SME/test/api/kleidicv-api-test
```

The output is similar to:

```
__output__ Vector length is set to 16 bytes.
__output__ Seed is set to 2542467924.
__output__ [==========] Running 3703 tests from 141 test suites.
__output__ [----------] Global test environment set-up.
__output__ [----------] 9 tests from SaturatingAddAbsWithThresholdTest/0, where TypeParam = short
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.TestPositive
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.TestPositive (0 ms)
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.TestNegative
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.TestNegative (0 ms)
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.TestMin
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.TestMin (0 ms)
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.TestZero
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.TestZero (0 ms)
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.TestMax
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.TestMax (0 ms)
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.NullPointer
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.NullPointer (0 ms)
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.Misalignment
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.Misalignment (0 ms)
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.ZeroImageSize
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.ZeroImageSize (0 ms)
__output__ [ RUN      ] SaturatingAddAbsWithThresholdTest/0.OversizeImage
__output__ [       OK ] SaturatingAddAbsWithThresholdTest/0.OversizeImage (0 ms)
__output__ [----------] 9 tests from SaturatingAddAbsWithThresholdTest/0 (0 ms total)
...
```

> **Note:** Currently, Apple Xcode is built on Clang 17. Version clang-1700.3.19.1 has an SME-related code generation bug that causes float `ResizeLinear` API tests to fail.

## Run the OpenCV test
After building OpenCV with KleidiCV, you will find the test binaries in the `build-opencv-kleidicv-sme/bin/` directory. The main tool for benchmarking image processing performance is `opencv_perf_imgproc`. This utility measures both execution speed and throughput for the OpenCV `imgproc` module, including KleidiCV-accelerated operations.

To focus your testing, use the `--gtest_filter` option to select specific tests and `--gtest_param_filter` to set test parameters. For example, you can run the Gaussian blur 5×5 performance test three times on a 1920x1080 grayscale image with replicated borders:

- Image size: 1920x1080 (Full HD)
- Image type: 8UC1 (8-bit unsigned, single channel)
- Border type: BORDER_REPLICATE

You can explore additional test cases and parameter combinations in the [benchmarks.txt](https://gitlab.arm.com/kleidi/kleidicv/-/blob/0.6.0/scripts/benchmark/benchmarks.txt?ref_type=tags) file in the KleidiCV repository.

The command for running the test is as follows:

```bash
./build-opencv-kleidicv-sme/bin/opencv_perf_imgproc \
  --gtest_filter='*gaussianBlur5x5/*' \
  --gtest_param_filter='(1920x1080, 8UC1, BORDER_REPLICATE)' \
  --gtest_repeat=3
```

The expected output is:

```
__output__ [ERROR:0@0.001] global persistence.cpp:566 open Can't open file: 'imgproc.xml' in read mode
__output__ TEST: Skip tests with tags: 'mem_6gb', 'verylong'
__output__ CTEST_FULL_OUTPUT
__output__ OpenCV version: 4.12.0
__output__ OpenCV VCS version: 4.12.0-2-g2eea907534
__output__ Build type: Release
__output__ Compiler: /usr/bin/c++  (ver 17.0.0.17000013)
...
```

## Understand KleidiCV multiversion backend support
The KleidiCV library detects the platform hardware at runtime and selects the backend implementation based on the following priority:

- SME2 backend implementation
- SME backend implementation
- SVE backend implementation
- Neon backend implementation

The following code shows how the library resolves which implementation to use:

```cpp
#define KLEIDICV_MULTIVERSION_C_API(api_name, neon_impl, sve2_impl, sme_impl, sme2_impl) \
  static decltype(neon_impl) api_name##_resolver() { \
    [[maybe_unused]] KLEIDICV_TARGET_NAMESPACE::HwCaps hwcaps = KLEIDICV_TARGET_NAMESPACE::get_hwcaps(); \
    KLEIDICV_SME2_RESOLVE(sme2_impl); \
    KLEIDICV_SME_RESOLVE(sme_impl); \
    KLEIDICV_SVE2_RESOLVE(sve2_impl); \
    return neon_impl; \
  } \
  extern "C" { \
    decltype(neon_impl) api_name = api_name##_resolver(); \
  }
```

It verifies SME support using the query `hw.optional.arm.FEAT_SME` as follows:

```cpp
#define KLEIDICV_SME_RESOLVE(sme_impl) \
  if (!std::is_null_pointer_v<decltype(sme_impl)> && \
      KLEIDICV_TARGET_NAMESPACE::query_sysctl("hw.optional.arm.FEAT_SME")) { \
    return sme_impl; \
  }
```

It verifies SME2 support using the query `hw.optional.arm.FEAT_SME2` as follows:

```cpp
#define KLEIDICV_SME2_RESOLVE(sme2_impl) \
  if (!std::is_null_pointer_v<decltype(sme2_impl)> && \
      KLEIDICV_TARGET_NAMESPACE::query_sysctl("hw.optional.arm.FEAT_SME2")) { \
    return sme2_impl; \
  }
```

## Enable debug information for backend implementation at runtime
To incorporate dump information for multiversion backend support during runtime testing, update `kleidicv/include/kleidicv/dispatch.h` as outlined below:

To patch `dispatch.h`, copy the entire code below and paste it in your terminal. It will run the `patch` command to insert the print statements to identify the backend.

```bash
patch -p1 -d "$HOME/kleidi" << 'EOF'
diff --git a/kleidicv/kleidicv/include/kleidicv/dispatch.h b/kleidicv/kleidicv/include/kleidicv/dispatch.h
index cc6ee01..44c98a5 100644
--- a/kleidicv/kleidicv/include/kleidicv/dispatch.h
+++ b/kleidicv/kleidicv/include/kleidicv/dispatch.h
@@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: 2023 - 2025 Arm Limited and/or its affiliates <open-source-office@arm.com>
+// SPDX-FileCopyrightText: 2024 - 2025 Arm Limited and/or its affiliates <open-source-office@arm.com>
 //
 // SPDX-License-Identifier: Apache-2.0

 #ifndef KLEIDICV_DISPATCH_H
 #define KLEIDICV_DISPATCH_H

+#include <stdio.h>
 #include "kleidicv/config.h"

 #if KLEIDICV_ENABLE_SME2 || KLEIDICV_ENABLE_SME || KLEIDICV_ENABLE_SVE2
@@ -33,6 +34,7 @@ static bool query_sysctl(const char* attribute_name) {
 #define KLEIDICV_SVE2_RESOLVE(sve2_impl)                                      \
   if (!std::is_null_pointer_v<decltype(sve2_impl)> &&                         \
       KLEIDICV_TARGET_NAMESPACE::query_sysctl("hw.optional.arm.FEAT_SVE2")) { \
+    printf("kleidicv API:: %s,SVE2 backend. \n", __func__);                    \
     return sve2_impl;                                                         \
   }
 #else
@@ -43,6 +45,7 @@ static bool query_sysctl(const char* attribute_name) {
 #define KLEIDICV_SME_RESOLVE(sme_impl)                                       \
   if (!std::is_null_pointer_v<decltype(sme_impl)> &&                         \
       KLEIDICV_TARGET_NAMESPACE::query_sysctl("hw.optional.arm.FEAT_SME")) { \
+    printf("kleidicv API:: %s,SME backend. \n", __func__);                  \
     return sme_impl;                                                         \
   }
 #else
@@ -53,6 +56,7 @@ static bool query_sysctl(const char* attribute_name) {
 #define KLEIDICV_SME2_RESOLVE(sme2_impl)                                      \
   if (!std::is_null_pointer_v<decltype(sme2_impl)> &&                         \
       KLEIDICV_TARGET_NAMESPACE::query_sysctl("hw.optional.arm.FEAT_SME2")) { \
+    printf("kleidicv API:: %s,SME2 backend. \n", __func__);                  \
     return sme2_impl;                                                         \
   }
 #else
@@ -67,6 +71,7 @@ static bool query_sysctl(const char* attribute_name) {
     KLEIDICV_SME2_RESOLVE(sme2_impl);                                         \
     KLEIDICV_SME_RESOLVE(sme_impl);                                           \
     KLEIDICV_SVE2_RESOLVE(sve2_impl);                                         \
+    printf("kleidicv API:: %s,NEON backend. \n", __func__);                   \
     return neon_impl;                                                         \
   }                                                                           \
   extern "C" {                                                                \
EOF
```

After making the change, rebuild the benchmark:

```bash
cmake --build build-kleidicv-benchmark-SME --parallel
```

## Extract Neon or SME backend data on a MacBook
After making the change and rebuilding for testing, you can display the SME backend usage summary as follows:

```bash
./build-kleidicv-benchmark-SME/benchmark/kleidicv-benchmark
```

The output starts by printing the backends followed by the benchmark output:

```
__output__ Kleidicv API:: kleidicv_min_max_u8_resolver,SME backend.
__output__ Kleidicv API:: kleidicv_min_max_s8_resolver,SME backend.
...
```

The output is truncated for brevity, but you will see detailed performance metrics for each operation at 1280x720 resolution. Look for lines showing the operation name, sample count, mean and median times, and standard deviation. These results help you compare the performance of different backends and confirm that SME or Neon acceleration is active.

## Use lldb to check the SME backend implementation
To perform source-level debugging during the build process, you must change the build type from `Release` to `Debug`, as demonstrated in the following example:

```bash
cmake -S $WORKSPACE/kleidicv \
      -B build-kleidicv-benchmark-SME \
      -DKLEIDICV_ENABLE_SME2=ON \
      -DKLEIDICV_LIMIT_SME2_TO_SELECTED_ALGORITHMS=OFF \
      -DKLEIDICV_BENCHMARK=ON \
      -DCMAKE_BUILD_TYPE=Debug
cmake --build build-kleidicv-benchmark-SME --parallel
```

Use the `lldb` debug tool to set breakpoints during API testing and verify if the SME backend implementation is invoked. To view the function call backtrace, run the `bt` command as shown below:

```bash
lldb ./build-kleidicv-benchmark-SME/test/api/kleidicv-api-test
```

The interactions with the `(lldb)` command line are shown below. Start by entering the following commands in the `lldb` debugger:

```bash
target create "./build-kleidicv-benchmark-SME/test/api/kleidicv-api-test"
b saturating_add_abs_with_threshold
run
```

When the program stops at your breakpoint, enter:

```bash
bt
```

This command displays the stack trace, showing how the function was called.

Next, to view the assembly instructions (including SME streaming mode), enter:

```bash
disassemble --frame
```

After you finish inspecting the output, exit `lldb` by typing:

```bash
quit
```

Note: Your file paths may differ, but the sequence of commands remains the same. Enter each command as shown and review the output at each step.

## Summary
In this Learning Path, you tested the KleidiCV build and verified its functionality. You ran both the KleidiCV API tests and the OpenCV performance tests. You also explored how KleidiCV’s multiversion support works, enabling it to select the optimal backend like SME, SVE, or Neon at runtime. Finally, you learned how to enable debug output and use the `lldb` debugger to confirm that the SME backend is being used and to inspect the assembly code.
