Repository navigation
Conversation
With CONFIG_LLEXT_TYPE_ELF_RELOCATABLE modules are partially linked (-r). A partial link keeps every C++ COMDAT group (inline functions, template instances, vtables) as a separate section, so C++ modules end up with thousands of sections: a wake-word module built with TensorFlow Lite Micro has 5721 sections and 1273 relocation sections. That breaks loading in several ways: - all executable sections except .text are linked at address 0 to run from DRAM, so 1639 of its 1640 code sections, including the hot TFLM convolution kernels, would execute from IMR - the section addresses are calculated from the module object only, so sections pulled in from libraries at the final link aren't assigned an address at all and are left at 0, also as "DRAM" sections, among them C++ vtables and read-only data - the LLEXT section map alone needs 45KiB, so llext_load() fails with -ENOMEM with the default 8KiB LLEXT heap, and with a bigger heap the module can't be mapped because .bss isn't next to .data any more First partially link all objects and libraries of a relocatable module into one object, with --force-group-allocation to dissolve the section groups and a linker script to merge all .text.*, .rodata.*, .data.* and .bss.* sections into one each, the same as a final link does. Then calculate addresses for that object. Specially named sections like .cold and .coldrodata aren't affected. If the linker doesn't support this, fall back to the previous behaviour with a warning. That module goes down to 25 sections, with only the .cold section in DRAM, and its file size from 1028KiB to 657KiB. Together with further fixes in the Zephyr LLEXT loader and in the module build, it then loads and runs on PTL, without any inference slowdown compared to building it into the base firmware. C modules mostly have a handful of sections anyway. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A partially linked Xtensa object keeps relocation entries which the LLEXT loader processes but doesn't act on: R_XTENSA_NONE and R_XTENSA_ASM_EXPAND ones, and R_XTENSA_SLOT0_OP ones for PC-relative L32R and CALLn references within a section, which the linker has already resolved, so the loader leaves the instruction unchanged. These are the vast majority: 12600 of 14764 relocations in a C++ module built with TensorFlow Lite Micro. Still for each of them the loader reads the relocation entry, the symbol and the instruction from the module image in IMR, where each cache miss costs about 1.7us on PTL. That took 73ms of the 86ms llext_load() time of that module. Remove them from the relocation sections after linking. For R_XTENSA_SLOT0_OP this uses the same check as the loader, so the result is identical. With this, the relocation pass of that module takes 21ms. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Large C++ LLEXT modules (e.g. anything built with TensorFlow Lite Micro) can't be loaded with
CONFIG_LLEXT_TYPE_ELF_RELOCATABLE. When they can be loaded,llext_load()is slow. This PR fixes both problems inllext_link_helper.py. Nothing changes in the firmware or the loader.Problem
The partial (
-r) link keeps every C++ COMDAT group (inline functions, template instances, vtables) as its own section. A wake-word module built with TFLM ends up with 5721 sections and 1273 relocation sections, and then:.textis linked at address 0, i.e. as a DRAM section, so 1639 of its 1640 code sections, including the hot TFLM kernels, would run from IMRllext_load()fails with-ENOMEMon the default 8KiB LLEXT heap; with a bigger heap,llext_manager_load_module()rejects the module because.bssisn't adjacent to.dataany moreChanges
--force-group-allocationand a small linker script (scripts/llext_merge_sections.ld) that folds.text.*,.rodata.*,.data.*and.bss.*into one section each..cold,.coldrodataand other specially named sections are untouched. If the linker rejects the option, the helper warns and falls back to the old behaviour.R_XTENSA_NONE,R_XTENSA_ASM_EXPAND, andR_XTENSA_SLOT0_OPfor PC-relative references within a section, which the linker has already resolved. The loader still reads each entry, its symbol and the instruction from the module image in IMR, at about 1.7µs per cache miss on PTL. The helper now removes them after linking, using the same check as the loader'sr_xtensa_slot0_op_relocate(), so the result is identical.Results
TFLM wake-word module on PTL (aphid), GCC (Zephyr SDK 1.0.1), first instantiation:
.coldin IMR).llextsizellext_load()-ENOMEM); ~208ms with a 64KiB heap, then mapping fails¹ including ~10KiB of libc/libgcc code that the module links in on the branch below.
The relocation pass alone went from 73ms to 21ms. Of the remaining ~20ms total, about half was building
ext->sym_tab, which a Zephyr change removes (see below). Mapping and copying into SRAM adds another 14–34ms, limited by IMR read bandwidth (~15MB/s).For C modules nothing much changes: they mostly have a handful of sections already, and the pruning only removes entries the loader ignores. I checked that
volumebuilds into the same layout either way.Dependencies / not in this PR
Loading C++ modules end to end on Xtensa also needs these Zephyr LLEXT fixes, on lgirdwood/zephyr
llext-cpp-modules(not sent upstream yet):STB_WEAKsymbolsR_XTENSA_PLText->sym_tabonly when it's usedsnprintk/vsnprintk,sys_clock_cycle_get_32andxtensa_arch_exceptThese are independent of this PR. The module-specific changes (extra exports, linking libc/libgcc into the module) are on lgirdwood/sof
llext-cpp-modules.Testing
volumeit produces the same sections and relocations as the in-tree build.--force-group-allocationsupport the helper falls back to the old behaviour.🤖 Generated with Claude Code