mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(compiler): add ESP-IDF LLVM optimization enablement framework
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
3
examples/system/llvm_memcpy_opt/.build-test-rules.yml
Normal file
3
examples/system/llvm_memcpy_opt/.build-test-rules.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
examples/system/llvm_memcpy_opt:
|
||||
enable:
|
||||
- if: IDF_TARGET == "esp32p4" and CONFIG_NAME == "default"
|
||||
7
examples/system/llvm_memcpy_opt/CMakeLists.txt
Normal file
7
examples/system/llvm_memcpy_opt/CMakeLists.txt
Normal 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)
|
||||
61
examples/system/llvm_memcpy_opt/README.md
Normal file
61
examples/system/llvm_memcpy_opt/README.md
Normal 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.
|
||||
18
examples/system/llvm_memcpy_opt/main/CMakeLists.txt
Normal file
18
examples/system/llvm_memcpy_opt/main/CMakeLists.txt
Normal 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")
|
||||
82
examples/system/llvm_memcpy_opt/main/llvm_memcpy_main.c
Normal file
82
examples/system/llvm_memcpy_opt/main/llvm_memcpy_main.c
Normal 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");
|
||||
}
|
||||
15
examples/system/llvm_memcpy_opt/main/memcpy_baseline.c
Normal file
15
examples/system/llvm_memcpy_opt/main/memcpy_baseline.c
Normal 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);
|
||||
}
|
||||
15
examples/system/llvm_memcpy_opt/main/memcpy_optimized.c
Normal file
15
examples/system/llvm_memcpy_opt/main/memcpy_optimized.c
Normal 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);
|
||||
}
|
||||
15
examples/system/llvm_memcpy_opt/pytest_llvm_memcpy_opt.py
Normal file
15
examples/system/llvm_memcpy_opt/pytest_llvm_memcpy_opt.py
Normal 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)
|
||||
2
examples/system/llvm_memcpy_opt/sdkconfig.defaults
Normal file
2
examples/system/llvm_memcpy_opt/sdkconfig.defaults
Normal file
@@ -0,0 +1,2 @@
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION=y
|
||||
3
examples/system/llvm_opt/.build-test-rules.yml
Normal file
3
examples/system/llvm_opt/.build-test-rules.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
examples/system/llvm_opt:
|
||||
enable:
|
||||
- if: IDF_TARGET == "esp32p4" and CONFIG_NAME == "default"
|
||||
7
examples/system/llvm_opt/CMakeLists.txt
Normal file
7
examples/system/llvm_opt/CMakeLists.txt
Normal 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)
|
||||
43
examples/system/llvm_opt/README.md
Normal file
43
examples/system/llvm_opt/README.md
Normal 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.
|
||||
11
examples/system/llvm_opt/main/CMakeLists.txt
Normal file
11
examples/system/llvm_opt/main/CMakeLists.txt
Normal 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()
|
||||
19
examples/system/llvm_opt/main/llvm_opt_main.c
Normal file
19
examples/system/llvm_opt/main/llvm_opt_main.c
Normal 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");
|
||||
}
|
||||
12
examples/system/llvm_opt/pytest_llvm_opt.py
Normal file
12
examples/system/llvm_opt/pytest_llvm_opt.py
Normal 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)
|
||||
2
examples/system/llvm_opt/sdkconfig.defaults
Normal file
2
examples/system/llvm_opt/sdkconfig.defaults
Normal file
@@ -0,0 +1,2 @@
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION=y
|
||||
Reference in New Issue
Block a user