fix(ldgen): correctly place symbols generated by compiler during IPA

As part of inter-procedural optimizations (IPA), the compiler may
perform tasks such as constant propagation for functions. This involves
generating a specialized version of a given function with a new symbol
name that includes a suffix. For example, during constant propagation,
the compiler might create a specialized version named
`spiflash_start_core.constprop.0` for the `spiflash_start_core`
function. Additionally, the compiler may generate multiple clones of a
single function. Currently, when ldgen performs symbol placement, it
does not account for these compiler-generated functions, leading to
their incorrect or unexpected placement in memory (markers).

Consider a linker fragment with:

```
[mapping:spi_flash]
archive: libspi_flash.a
entries:
    esp_flash_api: spiflash_start_core (noflash)
```

The `spiflash_start_core` function should be placed in IRAM. However,
the compiler might generate an optimized version of this function with a
`.constprop.0` suffix, resulting in a
`.text.spiflash_start_core.constprop.0` input section. Currently, ldgen
does not handle this situation, leading to misplaced symbols.

Since `.` is not allowed in C identifiers, it should be safe to consider
all input sections for a symbol with any `.` suffix as representing that
symbol. This means considering the symbol suffixes should not cause any
ambiguity.

This change automatically places all input sections, including those
with possible suffixes for a given symbol, into the specified memory. In
other words, specifying a function name like `spiflash_start_core` in a
linker fragment automatically includes input section names matching
`spiflash_start_core(\..*)?$`.

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
This commit is contained in:
Frantisek Hrbata
2025-12-10 08:37:58 +01:00
parent b685c0e733
commit 0d6ba577da
3 changed files with 1326 additions and 0 deletions
+13
View File
@@ -320,6 +320,19 @@ class ObjectNode(EntityNode):
if obj_sections:
symbol = entity.symbol
remove_sections = [s.replace('.*', '.%s' % symbol) for s in sections if '.*' in s]
# As part of IPA optimization, the compiler may perform
# constant propagation and generate specialized versions of a
# function. For example, for the function spiflash_start_core,
# the compiler might also generate a
# spiflash_start_core.constprop.0 symbol, which will be placed
# in a separate input section named
# .text.spiflash_start_core.constprop.0. Ensure that such
# generated functions are placed into the appropriate marker as
# well.
remove_sections_patterns = [s.replace('.*', f'.{symbol}.*') for s in sections if '.*' in s]
for pattern in remove_sections_patterns:
remove_sections.extend(fnmatch.filter(obj_sections, pattern))
filtered_sections = [s for s in obj_sections if s not in remove_sections]
if set(filtered_sections) != set(obj_sections):
File diff suppressed because it is too large Load Diff
+71
View File
@@ -33,6 +33,7 @@ FREERTOS = Entity('libfreertos.a')
CROUTINE = Entity('libfreertos.a', 'croutine')
TIMERS = Entity('libfreertos.a', 'timers')
TEMPERATURE_SENSOR_PERIPH = Entity('libsoc.a', 'temperature_sensor_periph')
ESP_FLASH_API = Entity('libspi_flash.a', 'esp_flash_api')
FREERTOS2 = Entity('libfreertos2.a')
@@ -72,6 +73,9 @@ class GenerationTest(unittest.TestCase):
with open('data/libsoc.a.txt') as objdump:
self.entities.add_sections_info(objdump)
with open('data/libspi_flash.a.txt') as objdump:
self.entities.add_sections_info(objdump)
with open('data/linker_script.ld') as linker_script:
self.linker_script_expect = LinkerScript(linker_script)
@@ -340,6 +344,73 @@ entries:
self.compare_rules(expected, actual)
def test_nondefault_mapping_symbol_with_suffix(self):
# Test a mapping entry that differs from the default for a symbol
# generated by the compiler, such as those created during IPA
# optimization for constant propagation.
#
# There should be exclusions in the default commands for flash_text, as
# well as the implicit intermediate object command with an exclusion
# from default:
#
# flash_text
# *((EXCLUDE_FILE(*libspi_flash.a:esp_flash_api.*)) .text ...) A
# *libspi_flash.a:esp_flash_api.*(.text.check_chip_pointer_default ...) B
#
# Commands for placing the generated symbol in iram should be created,
# and they must also include .text.spiflash_start_core.constprop.0,
# even though the placement is specified only for the
# spiflash_start_core symbol.
#
# iram0_text
# *(.iram ...)
# *libspi_flash.a:esp_flash_api.*(.literal.spiflash_start_core .text.spiflash_start_core
# .text.spiflash_start_core.constprop.0) C
mapping = """
[mapping:test]
archive: libspi_flash.a
entries:
esp_flash_api:spiflash_start_core (noflash) #1
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
iram0_text = expected['iram0_text']
# Generate exclusion in flash_text A
flash_text[0].exclusions.add(ESP_FLASH_API)
# Generate intermediate command B
# List all relevant sections except the symbol
# being mapped
esp_flash_api_sections = self.entities.get_sections('libspi_flash.a', 'esp_flash_api')
filtered_sections = fnmatch.filter(esp_flash_api_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(esp_flash_api_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('spiflash_start_core.constprop.0')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(ESP_FLASH_API, set(filtered_sections), []))
# Input section commands in iram_text for #1 C
iram0_text.append(
InputSectionDesc(
ESP_FLASH_API,
set(
[
'.literal.spiflash_start_core',
'.text.spiflash_start_core',
'.text.spiflash_start_core.constprop.0',
]
),
[],
)
)
self.compare_rules(expected, actual)
def test_nondefault_mapping_all_symbols(self):
# Test mapping entry different from default for all .rodata.* symbols in the temperature_sensor_periph
# object file. There should be exclusion in the default commands for flash_rodata, but