Merge branch 'feat/llvm-opt-integration' into 'master'

feat(compiler): add ESP-IDF LLVM optimization enablement framework

See merge request espressif/esp-idf!51154
This commit is contained in:
Marius Vikhammer
2026-09-07 10:11:37 +08:00
24 changed files with 742 additions and 2 deletions

View File

@@ -0,0 +1,3 @@
examples/system/llvm_memcpy_opt:
enable:
- if: IDF_TARGET == "esp32p4" and CONFIG_NAME == "default"

View File

@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON)
project(llvm_memcpy_opt)

View File

@@ -0,0 +1,61 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |
# LLVM memcpy optimization
Benchmark for the ESP-IDF LLVM optimization framework: `menuconfig` enables
the memcpy optimization, and CMake marks which sources receive it.
This example compares two source files in the same component implementing the
same 16-byte aligned, fixed 1024-byte `memcpy`:
- `memcpy_baseline.c`: built without LLVM optimization.
- `memcpy_optimized.c`: selected with
`idf_component_enable_llvm_opt(SRCS "memcpy_optimized.c")`.
The project uses performance optimization. When **Compiler options > LLVM
optimizations > Optimize memcpy with PIE** is enabled, ESP-IDF injects the
framework flags only into `memcpy_optimized.c`.
Use the Espressif Clang toolchain (`IDF_TOOLCHAIN=clang`) for the memcpy
speedup. With the default GCC toolchain the example still **builds and runs**
(so CI can board-test correctness on ESP32-P4): the LLVM opt-in markers are
ignored and CMake prints a warning with the Clang commands below. Cycle counts
under GCC are not a meaningful “optimized vs baseline” comparison.
Both ESP32-P4 ECO4 (PIE 2.1) and ECO5+ (PIE 2.2) are supported. The framework
picks the PIE feature from the chip revision selected in menuconfig (IDF
default is ECO5+ / rev >= 3.0). For an ECO4 board, set the revision under
**Component config > ESP System Settings** (or equivalent) to a rev < 3.0
option so `CONFIG_ESP32P4_SELECTS_REV_LESS_V3` is enabled. No example-specific
revision pin is required.
## Build and run
```bash
cd "$IDF_PATH/examples/system/llvm_memcpy_opt"
IDF_TOOLCHAIN=clang idf.py set-target esp32p4
IDF_TOOLCHAIN=clang idf.py build flash monitor
```
`sdkconfig.defaults` enables the menuconfig option for this example. It can
also be changed with:
```bash
IDF_TOOLCHAIN=clang idf.py menuconfig
```
The example verifies both copies against the source, then prints minimum cycle
counts from 512 runs (benchmark helper placed in IRAM). Example ECO4 / PIE 2.1
output:
```text
LLVM memcpy optimization: enabled
Copy: 1024 bytes, source/destination alignment: 16 bytes
Baseline cycles: 671
Optimized cycles: 136
Result: PASS
```
Cycle counts depend on chip revision, clock configuration, toolchain version,
and memory placement. Use values printed by the target as benchmark results.

View File

@@ -0,0 +1,18 @@
idf_component_register(SRCS "llvm_memcpy_main.c"
"memcpy_baseline.c"
"memcpy_optimized.c"
INCLUDE_DIRS "")
# Framework markers are ignored on non-Clang (by design). Keep the example
# buildable under the default GCC example CI jobs so P4 board tests can run;
# print a clear warning so local users still know to use Clang for speedup.
if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
message(WARNING
"examples/system/llvm_memcpy_opt: compiler is not Clang; "
"ENABLE_LLVM_OPT / idf_component_enable_llvm_opt are ignored. "
"For the memcpy optimization use:\n"
" IDF_TOOLCHAIN=clang idf.py set-target esp32p4\n"
" IDF_TOOLCHAIN=clang idf.py build flash monitor")
endif()
idf_component_enable_llvm_opt(SRCS "memcpy_optimized.c")

View File

@@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "esp_attr.h"
#include "esp_cpu.h"
#include "freertos/FreeRTOS.h"
#include "sdkconfig.h"
#define COPY_SIZE 1024
#define BENCHMARK_RUNS 512
#if CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION
#define LLVM_MEMCPY_STATUS "enabled"
#else
#define LLVM_MEMCPY_STATUS "disabled"
#endif
void memcpy_baseline_1024(void *dst, const void *src);
void memcpy_optimized_1024(void *dst, const void *src);
typedef void (*copy_fn_t)(void *, const void *);
static _Alignas(16) uint8_t s_source[COPY_SIZE];
static _Alignas(16) uint8_t s_baseline_destination[COPY_SIZE];
static _Alignas(16) uint8_t s_optimized_destination[COPY_SIZE];
static portMUX_TYPE s_benchmark_lock = portMUX_INITIALIZER_UNLOCKED;
static IRAM_ATTR uint32_t benchmark(copy_fn_t copy, uint8_t *destination)
{
uint32_t best = UINT32_MAX;
for (int i = 0; i < 16; ++i) {
copy(destination, s_source);
}
for (int i = 0; i < BENCHMARK_RUNS; ++i) {
portENTER_CRITICAL(&s_benchmark_lock);
__asm__ volatile ("" ::: "memory");
uint32_t start = esp_cpu_get_cycle_count();
copy(destination, s_source);
uint32_t end = esp_cpu_get_cycle_count();
__asm__ volatile ("" ::: "memory");
portEXIT_CRITICAL(&s_benchmark_lock);
uint32_t cycles = end - start;
if (cycles < best) {
best = cycles;
}
}
return best;
}
void app_main(void)
{
for (int i = 0; i < COPY_SIZE; ++i) {
s_source[i] = (uint8_t)(i * 3 + 1);
}
memcpy_baseline_1024(s_baseline_destination, s_source);
memcpy_optimized_1024(s_optimized_destination, s_source);
bool result_matches = memcmp(s_baseline_destination, s_optimized_destination, COPY_SIZE) == 0
&& memcmp(s_source, s_optimized_destination, COPY_SIZE) == 0;
uint32_t baseline_cycles = benchmark(memcpy_baseline_1024, s_baseline_destination);
uint32_t optimized_cycles = benchmark(memcpy_optimized_1024, s_optimized_destination);
printf("LLVM memcpy optimization: %s\n", LLVM_MEMCPY_STATUS);
printf("Copy: %d bytes, source/destination alignment: 16 bytes\n", COPY_SIZE);
printf("Baseline cycles: %" PRIu32 "\n", baseline_cycles);
printf("Optimized cycles: %" PRIu32 "\n", optimized_cycles);
printf("Result: %s\n", result_matches ? "PASS" : "FAIL");
}

View File

@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <string.h>
__attribute__((noinline))
void memcpy_baseline_1024(void *dst, const void *src)
{
dst = __builtin_assume_aligned(dst, 16);
src = __builtin_assume_aligned(src, 16);
memcpy(dst, src, 1024);
}

View File

@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <string.h>
__attribute__((noinline))
void memcpy_optimized_1024(void *dst, const void *src)
{
dst = __builtin_assume_aligned(dst, 16);
src = __builtin_assume_aligned(src, 16);
memcpy(dst, src, 1024);
}

View File

@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
def test_llvm_memcpy_opt(dut: Dut) -> None:
"""Board smoke: correctness PASS. Speedup needs Clang + menuconfig option."""
dut.expect(r'LLVM memcpy optimization: (enabled|disabled)', timeout=60)
dut.expect(r'Baseline cycles:\s+\d+', timeout=30)
dut.expect(r'Optimized cycles:\s+\d+', timeout=30)
dut.expect_exact('Result: PASS', timeout=30)

View File

@@ -0,0 +1,2 @@
CONFIG_COMPILER_OPTIMIZATION_PERF=y
CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION=y

View File

@@ -0,0 +1,3 @@
examples/system/llvm_opt:
enable:
- if: IDF_TARGET == "esp32p4" and CONFIG_NAME == "default"

View File

@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON)
project(llvm_opt)

View File

@@ -0,0 +1,43 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |
# LLVM optimization (component scope)
Minimal example for the ESP-IDF LLVM optimization framework. It shows how to
apply the LLVM optimizations selected in menuconfig to **an entire component**
with the `ENABLE_LLVM_OPT` argument of `idf_component_register`:
```cmake
idf_component_register(SRCS "llvm_opt_main.c"
INCLUDE_DIRS ""
ENABLE_LLVM_OPT)
```
Enable the option in menuconfig: **Compiler options > LLVM optimizations >
Optimize memcpy with PIE**. On ESP32-P4 with the Clang toolchain, ESP-IDF then
injects the corresponding compiler flags into every source file of this
component.
With the default GCC toolchain the example still builds (for CI board smoke
tests); `ENABLE_LLVM_OPT` is ignored and CMake prints a warning. Use Clang for
real LLVM opt flags:
## Build and run
Activate the Clang toolchain before configuring the project:
```bash
cd "$IDF_PATH/examples/system/llvm_opt"
IDF_TOOLCHAIN=clang idf.py set-target esp32p4
IDF_TOOLCHAIN=clang idf.py build flash monitor
```
`CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION` is enabled by `sdkconfig.defaults`
and can be changed with `idf.py menuconfig`.
## Related example
For the file-level API (`idf_component_enable_llvm_opt`) and a `memcpy`
benchmark comparing optimized and baseline sources, see the
`llvm_memcpy_opt` example.

View File

@@ -0,0 +1,11 @@
idf_component_register(SRCS "llvm_opt_main.c"
INCLUDE_DIRS ""
ENABLE_LLVM_OPT)
if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
message(WARNING
"examples/system/llvm_opt: compiler is not Clang; "
"ENABLE_LLVM_OPT is ignored. For real LLVM opt flags use:\n"
" IDF_TOOLCHAIN=clang idf.py set-target esp32p4\n"
" IDF_TOOLCHAIN=clang idf.py build flash monitor")
endif()

View File

@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include "sdkconfig.h"
void app_main(void)
{
#if CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION
printf("LLVM optimization for ESP32-P4 memcpy: enabled in menuconfig\n");
#else
printf("LLVM optimization for ESP32-P4 memcpy: not enabled (select it in menuconfig)\n");
#endif
printf("This component is built with ENABLE_LLVM_OPT.\n");
}

View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
def test_llvm_opt(dut: Dut) -> None:
dut.expect(r'LLVM optimization for ESP32-P4 memcpy:', timeout=30)
dut.expect_exact('This component is built with ENABLE_LLVM_OPT.', timeout=10)

View File

@@ -0,0 +1,2 @@
CONFIG_COMPILER_OPTIMIZATION_PERF=y
CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION=y