Files
esp-idf/tools/ldgen/test/test_generation.py
Frantisek Hrbata 0949409f1b 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>
2025-12-10 12:26:33 +01:00

1965 lines
79 KiB
Python
Executable File

#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
import collections
import fnmatch
import os
import sys
import tempfile
import unittest
from io import StringIO
try:
from ldgen.entity import Entity
from ldgen.entity import EntityDB
from ldgen.fragments import parse_fragment_file
from ldgen.generation import Generation
from ldgen.generation import GenerationException
from ldgen.linker_script import LinkerScript
from ldgen.output_commands import AlignAtAddress
from ldgen.output_commands import InputSectionDesc
from ldgen.output_commands import SymbolAtAddress
from ldgen.sdkconfig import SDKConfig
except ImportError:
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from ldgen.entity import Entity
from ldgen.entity import EntityDB
from ldgen.fragments import parse_fragment_file
from ldgen.generation import Generation
from ldgen.generation import GenerationException
from ldgen.linker_script import LinkerScript
from ldgen.output_commands import AlignAtAddress
from ldgen.output_commands import InputSectionDesc
from ldgen.output_commands import SymbolAtAddress
from ldgen.sdkconfig import SDKConfig
ROOT = Entity('*')
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')
class GenerationTest(unittest.TestCase):
def setUp(self):
self.generation = Generation()
self.entities = None
self.linker_script = None
with tempfile.NamedTemporaryFile(delete=False) as f:
self.kconfigs_source_file = os.path.join(tempfile.gettempdir(), f.name)
self.addCleanup(os.remove, self.kconfigs_source_file)
with tempfile.NamedTemporaryFile(delete=False) as f:
self.kconfig_projbuilds_source_file = os.path.join(tempfile.gettempdir(), f.name)
self.addCleanup(os.remove, self.kconfig_projbuilds_source_file)
os.environ['COMPONENT_KCONFIGS_SOURCE_FILE'] = self.kconfigs_source_file
os.environ['COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE'] = self.kconfig_projbuilds_source_file
os.environ['COMPONENT_KCONFIGS'] = ''
os.environ['COMPONENT_KCONFIGS_PROJBUILD'] = ''
# prepare_kconfig_files.py doesn't have to be called because COMPONENT_KCONFIGS and
# COMPONENT_KCONFIGS_PROJBUILD are empty
self.sdkconfig = SDKConfig('data/Kconfig', 'data/sdkconfig')
fragment_file = parse_fragment_file('data/base.lf', self.sdkconfig)
self.generation.add_fragments_from_file(fragment_file)
self.entities = EntityDB()
with open('data/libfreertos.a.txt') as objdump:
self.entities.add_sections_info(objdump)
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)
with open('data/linker_script.ld') as linker_script:
self.linker_script_actual = LinkerScript(linker_script)
@staticmethod
def create_fragment_file(contents, name='test_fragment.lf'):
f = StringIO(contents)
f.name = name
return f
def add_fragments(self, text):
fragment_file = self.create_fragment_file(text)
fragment_file = parse_fragment_file(fragment_file, self.sdkconfig)
self.generation.add_fragments_from_file(fragment_file)
def write(self, expected, actual):
self.linker_script_expect.fill(expected)
self.linker_script_expect.write(open('expected.ld', 'w'))
self.linker_script_actual.fill(actual)
self.linker_script_actual.write(open('actual.ld', 'w'))
def generate_default_rules(self):
rules = collections.defaultdict(list)
rules['dram0_bss'].append(InputSectionDesc(ROOT, ['.bss', '.bss.*'], []))
rules['dram0_bss'].append(InputSectionDesc(ROOT, ['COMMON'], []))
rules['dram0_data'].append(InputSectionDesc(ROOT, ['.data', '.data.*'], []))
rules['dram0_data'].append(InputSectionDesc(ROOT, ['.dram', '.dram.*'], []))
rules['flash_text'].append(InputSectionDesc(ROOT, ['.literal', '.literal.*', '.text', '.text.*'], []))
rules['flash_rodata'].append(InputSectionDesc(ROOT, ['.rodata', '.rodata.*'], []))
rules['iram0_text'].append(InputSectionDesc(ROOT, ['.iram', '.iram.*'], []))
rules['rtc_bss'].append(InputSectionDesc(ROOT, ['.rtc.bss'], []))
rules['rtc_data'].append(InputSectionDesc(ROOT, ['.rtc.data'], []))
rules['rtc_data'].append(InputSectionDesc(ROOT, ['.rtc.rodata'], []))
rules['rtc_text'].append(InputSectionDesc(ROOT, ['.rtc.text', '.rtc.literal'], []))
return rules
def compare_rules(self, expected, actual):
self.assertEqual(expected, actual)
def get_default(self, target, rules):
return rules[target][0]
class DefaultMappingTest(GenerationTest):
def test_rule_generation_default(self):
# Checks that default rules are generated from
# the default scheme properly and even if no mappings
# are defined.
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
self.compare_rules(expected, actual)
def test_default_mapping_lib(self):
# Mapping a library with default mapping. This should not emit additional rules,
# other than the default ones.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
* (default)
"""
self.add_fragments(mapping)
self.test_rule_generation_default()
def test_default_mapping_obj(self):
# Mapping an object with default mapping. This should not emit additional rules,
# other than the default ones.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (default)
"""
self.add_fragments(mapping)
self.test_rule_generation_default()
def test_default_mapping_symbol(self):
# Mapping a symbol with default mapping. This should not emit additional rules,
# other than the default ones.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine:prvCheckPendingReadyList (default) #1
"""
self.add_fragments(mapping)
self.test_rule_generation_default()
def test_default_mapping_all(self):
# Mapping a library, object, and symbol with default mapping. This should not emit additional rules,
# other than the default ones.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
* (default) #1
croutine (default) #2
croutine:prvCheckPendingReadyList (default) #3
"""
self.add_fragments(mapping)
self.test_rule_generation_default()
def test_default_mapping_lib_symbol(self):
# Mapping a library, and symbol with default mapping. This should not emit additional rules,
# other than the default ones.
#
# This is a check needed to make sure generation does not generate
# intermediate commands due to presence of symbol mapping.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
* (default) #1
croutine:prvCheckPendingReadyList (default) #2
"""
self.add_fragments(mapping)
self.test_rule_generation_default()
def test_default_mapping_obj_symbol(self):
# Mapping a library, and symbol with default mapping. This should not emit additional rules,
# other than the default ones.
#
# This is a check needed to make sure generation does not generate
# intermediate commands due to presence of symbol mapping.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (default) #1
croutine:prvCheckPendingReadyList (default) #2
"""
self.add_fragments(mapping)
self.test_rule_generation_default()
class MutableMappingTest(GenerationTest):
# Collection of tests for mutable library mappings
def dump_rules(self, rules):
# A simple helper for displaying the rules. It can be used to manually
# compare the actual and expected rules to identify any issues while
# preparing tests.
print()
for target, section_descs in rules.items():
print(f'target: {target}')
for section_desc in section_descs:
print(f' {section_desc.entity}: {section_desc.sections}')
for exclusion in section_desc.exclusions:
print(f' {exclusion}')
def add_mutable_libs_to_rules(self, libs, default_rules):
# Insert mutable lib rules and exclusions into default test rules.
rules = collections.defaultdict(list)
for target, section_descs in default_rules.items():
for section_desc in section_descs:
# Duplicate the current section description.
section_desc_copy = InputSectionDesc(section_desc.entity, section_desc.sections)
for lib in libs:
# Exclude each mutable library from ROOT entity rule.
section_desc_copy.exclusions.add(Entity(lib))
rules[target].append(section_desc_copy)
# Add a rule for each mutable library and each ROOT entity rule section.
# This is done separately because the order is important.
for lib in libs:
for section_desc in section_descs:
rules[target].append(InputSectionDesc(Entity(lib), section_desc.sections))
return rules
def test_rule_generation_default(self):
# Verifies that the default rules are correctly generated from the
# default scheme and that mutable libraries are included in the
# generated rules.
mutable_libs = ['libmutable.a', 'libmutable2.a']
self.generation.mutable_libs = mutable_libs
actual = self.generation.generate(self.entities, False)
default_rules = self.generate_default_rules()
expected = self.add_mutable_libs_to_rules(mutable_libs, default_rules)
self.compare_rules(expected, actual)
self.generation.mutable_libs = []
def test_default_mapping_obj(self):
# Verifies that libfreertos.a:croutine entity is correctly excluded
# from the libfreertos.a entity and moved to iram and dram.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash)
"""
mutable_libs = ['libfreertos.a']
self.generation.mutable_libs = mutable_libs
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
default_rules = self.generate_default_rules()
expected = self.add_mutable_libs_to_rules(mutable_libs, default_rules)
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Add exclusions for the libfreertos.a:croutine entity into
# libfreertos.a entity in test and rodata.
flash_text[1].exclusions.add(CROUTINE)
flash_rodata[1].exclusions.add(CROUTINE)
# Add rules for the libfreertos.a:croutine entity in iram and dram.
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[1].sections))
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[1].sections))
self.compare_rules(expected, actual)
class BasicTest(GenerationTest):
# Test basic and fundamental interactions between typical
# entries.
def test_nondefault_mapping_lib(self, alt=None):
# Test mapping entry different from default for a library.
# There should be exclusions in the default commands for flash_text and flash_rodata:
#
# flash_text
# *((EXCLUDE_FILE(libfreertos.a)) .literal ...) A
#
# Commands placing the entire library in iram, dram should be generated:
#
# iram0_text
# *(.iram ...)
# *libfreertos.a(.literal ...) B
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
* (noflash) #1
"""
self.add_fragments(alt if alt else mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Generate exclusions in flash_text and flash_rodata A
flash_text[0].exclusions.add(FREERTOS)
flash_rodata[0].exclusions.add(FREERTOS)
# Input section commands in iram_text and dram0_data for #1 B
iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, []))
dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, []))
self.compare_rules(expected, actual)
def test_nondefault_mapping_obj(self, alt=None):
# Test mapping entry different from default for an object.
# There should be exclusions in the default commands for flash_text and flash_rodata:
#
# flash_text
# *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A
#
# Commands placing the entire library in iram, dram should be generated:
#
# iram0_text
# *(.iram ...)
# *libfreertos.a:croutine(.literal ...) B
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash) #1
"""
self.add_fragments(alt if alt else mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Generate exclusions in flash_text and flash_rodata A
flash_text[0].exclusions.add(CROUTINE)
flash_rodata[0].exclusions.add(CROUTINE)
# Input section commands in iram_text and dram0_data for #1 B
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
self.compare_rules(expected, actual)
def test_nondefault_mapping_symbol(self):
# Test mapping entry different from default for symbol.
# 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(libfreertos.a:croutine)) .literal ...) A
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) B
#
# Commands placing the entire library in iram should be generated:
#
# iram0_text
# *(.iram ...)
# *libfreertos.a:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) C
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine:prvCheckPendingReadyList (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(CROUTINE)
# Generate intermediate command B
# List all relevant sections except the symbol
# being mapped
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
# Input section commands in iram_text for #1 C
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
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
# no implicit intermediate object command(X), because there are no .rodata+
# symbols left to be placed in dram0_data.
#
# The X line with entity only(without any input sections) should not be emitted, because
# linker would include all not yet placed input sections from the temperature_sensor_periph
# object file, including .debug, .comment and other input section.
#
# flash.rodata
# *((EXCLUDE_FILE(*libsoc.a:temperature_sensor_periph.*)) .rodata.* ...) A
# # *libsoc.a:temperature_sensor_periph.* X
#
# Commands placing the entire library in iram should be generated:
#
# dram0_data
# *libsoc.a:temperature_sensor_periph.*(.rodata.temperature_sensor_attribute) B
mapping = """
[mapping:test]
archive: libsoc.a
entries:
temperature_sensor_periph:temperature_sensor_attributes (noflash) # 1
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_rodata = expected['flash_rodata']
dram0_data = expected['dram0_data']
# Generate exclusion in flash_text A
flash_rodata[0].exclusions.add(TEMPERATURE_SENSOR_PERIPH)
# Input section commands in dram0_data for #1 B
dram0_data.append(
InputSectionDesc(TEMPERATURE_SENSOR_PERIPH, set(['.rodata.temperature_sensor_attributes']), [])
)
self.compare_rules(expected, actual)
def test_default_symbol_nondefault_lib(self):
# Test default symbol mapping with different lib mapping. This should
# create an implicit intermediate object command.
# The significant targets are flash_text, flash_rodata, iram0_text, dram0_data.
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a) .text ...) A
# libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) B
#
# flash_rodata
# *(EXCLUDE_FILE(libfreertos.a) .rodata ...) A
#
# iram0_text
# * ( .iram ...)
# libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .text ...) C.1
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) D
#
# dram0_data
# * ( .dram ...)
# libfreertos.a ( .rodata ...) C.2
#
# Only default commands are in the other targets.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
* (noflash) #1
croutine:prvCheckPendingReadyList (default) #2
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Exclusions for #1 A
flash_text[0].exclusions.add(FREERTOS)
flash_rodata[0].exclusions.add(FREERTOS)
# Commands for #1 C.1 & C.2
# C.1 excludes intermediate command for #2
iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE]))
dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, []))
# Intermediate command for excluding #2 D
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
# Command for #2 B
flash_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_default_symbol_nondefault_obj(self):
# Test default symbol mapping with different obj mapping. Since there is an explicit entry for the object,
# the sections for that object should just be expanded and the symbol section subtracted, to be placed
# using another command.
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A
# libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) B
#
# flash_rodata
# *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) A
#
# iram0_text
# *( .iram ...)
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) C.1
#
# dram0_data
# *(.data ..)
# *libfreertos.a:croutine(.rodata ....) C.2
#
# Only default commands are in the other targets
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash) #1
croutine:prvCheckPendingReadyList (default) #2
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Exclusions for #1 A
flash_text[0].exclusions.add(CROUTINE)
flash_rodata[0].exclusions.add(CROUTINE)
# Commands for #1 C.1 & C.2
# C.1 list relevant sections for libfreertos.a:croutine to
# exclude symbol to map
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
# Command for #2 B
flash_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_default_nondefault_alternating(self):
# Here, each of the entries map sections to something different
# than its one-level-up entry.
#
# * text -> flash, rodata -> flash
# libfreertos.a text -> iram, rodata -> dram
# libfreertos.a:croutine text -> flash, rodata -> flash
# croutine:prvCheckPendingReadyList text -> iram
#
# The significant targets are flash_text, flash_rodata, iram0_text, and dram0_data.
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a) .text ...) A
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) B.1
#
# flash_rodata
# *(EXCLUDE_FILE(libfreertos.a) .rodata ...) A
# *libfreertos.a:croutine(.rodata .rodata.*) B.2
#
# iram0_text
# * ( .iram ...)
# libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .text ...) C
# libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) D
#
# dram0_data
# * ( .dram ...)
# libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .rodata ...) C
#
# For the other targets only the default commands should be present.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
* (noflash) #1
croutine (default) #2
croutine:prvCheckPendingReadyList (noflash) #3
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Exclusions for #1 A
# Only for flash_text and flash_rodata
flash_text[0].exclusions.add(FREERTOS)
flash_rodata[0].exclusions.add(FREERTOS)
# Commands for #1 C
# with exclusions for #2
iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE]))
dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, [CROUTINE]))
# Commands for #2 B.1
flash_rodata.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
# List all relevant sections in case of flash_text B.2
# as exclusion for #3
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
# Command for #3 D
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_nondefault_but_same_lib_and_obj(self):
# Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping
# to #1. Output is similar to test_different_mapping_lib.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
* (noflash) #1
croutine (noflash) #2
"""
self.test_nondefault_mapping_lib(mapping)
def test_nondefault_but_same_lib_and_sym(self):
# Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping
# to #1. Output is similar to test_different_mapping_lib.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
* (noflash) #1
croutine:prvCheckPendingReadyList (noflash) #2
"""
self.test_nondefault_mapping_lib(mapping)
def test_nondefault_but_same_obj_and_sym(self):
# Commands should not be generated for #2, since it does similar mapping
# to #1. Output is similar to test_different_mapping_obj.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash) #1
croutine:prvCheckPendingReadyList (noflash) #2
"""
self.test_nondefault_mapping_obj(mapping)
def test_multiple_symbols_excluded_from_intermediate_command(self):
# Test mapping multiple symbols from the same object.
# All these symbols must be successfully excluded from
# the intermediate command.
#
# flash_text
# * (EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A
# libfreertos:croutine(.text ...) B
#
# iram0_text
#
#
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine:prvCheckPendingReadyList (noflash) #1
croutine:prvCheckDelayedList (noflash) #2
"""
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']
# Exclusions for #1 & #2 intermediate command A
flash_text[0].exclusions.add(CROUTINE)
# Intermediate command for #1 & #2 which lists B
# all relevant sections in croutine except prvCheckPendingReadyList
# and prvCheckDelayedList
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckDelayedList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
# Commands for #1 & 2
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), [])
)
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_root_mapping_fragment(self):
# Test creation of a mapping fragment that maps '*'.
# This should generate another default command in iram0_text:
#
# iram0_text
# * (.custom_section) A
# * (.iram .iram.*)
mapping = """
[sections:custom_section]
entries:
.custom_section
[scheme:custom_scheme]
entries:
custom_section -> iram0_text
[mapping:default2]
archive: *
entries:
* (custom_scheme) #1
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
# Generate default command A
# Since these are the same 'specificity', the commands
# are arranged alphabetically.
expected['iram0_text'].append(expected['iram0_text'][0])
expected['iram0_text'][0] = InputSectionDesc(ROOT, ['.custom_section'], [])
self.compare_rules(expected, actual)
class AdvancedTest(GenerationTest):
# Test valid but quirky cases, corner cases, failure cases, and
# cases involving interaction between schemes, other mapping
# fragments.
def test_same_entity_no_scheme_common(self):
# Test same entity being mapped by schemes that have nothing in common.
#
# noflash_data: rodata -> dram0_data
# noflash_text: text -> iram0_text
#
# This operation should succeed with the following commands:
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A
#
# flash_rodata
# *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) B
#
# iram0_text
# *(.iram ...)
# *libfreertos.a:croutine(.text .text.* ...) C
#
# dram0_data
# *(.data ..)
# *(.dram ...)
# *libfreertos.a:croutine(.rodata .rodata.*) D
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash_text) #1
croutine (noflash_data) #2
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Exclusions for #1 A
flash_text[0].exclusions.add(CROUTINE)
# Exclusions for #2 B
flash_rodata[0].exclusions.add(CROUTINE)
# Command for #1 C
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
# Command for #2 D
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
self.compare_rules(expected, actual)
def test_same_entity_sub_scheme(self):
# Test same entity being mapped by scheme that is a subset of the other.
#
# noflash: text -> iram0_text, rodata -> dram0_data
# noflash_text: text -> iram0_text
#
# `text -> iram0_text` is common between the two schemes.
#
# This operation should succeed with the following commands:
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A
#
# flash_rodata
# *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) B
#
# iram0_text
# *(.iram ...)
# *libfreertos.a:croutine(.text .text.* ...) C
#
# dram0_data
# *(.data ..)
# *(.dram ...)
# *libfreertos.a:croutine(.rodata .rodata.*) D
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash) #1
croutine (noflash_data) #2
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Exclusions for #1 A
flash_text[0].exclusions.add(CROUTINE)
# Exclusions for #1 & #2 B
flash_rodata[0].exclusions.add(CROUTINE)
# Command for #1 C
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
# Command for #1 & #2 D
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
self.compare_rules(expected, actual)
def test_same_entity_conflicting_scheme(self, alt=None):
# Test same entity being mapped by scheme conflicting with another.
#
# rtc = text -> rtc_text, rodata -> rtc_data
# noflash = text -> iram0_text, rodata -> dram0_data
#
# This operation should fail.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash) #1
croutine (rtc) #2
"""
self.add_fragments(alt if alt else mapping)
with self.assertRaises(GenerationException):
self.generation.generate(self.entities, False)
def test_same_entity_conflicting_section(self):
# Test same entity being mapped by scheme conflicting with another.
#
# custom_rtc = .text -> rtc_text
# noflash = .text -> iram0_text, .rodata -> dram0_data
#
# This operation should fail.
mapping = """
[sections:custom_text]
entries:
.text+
.literal+
[scheme:custom_rtc]
entries:
custom_text -> rtc_text
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash) #1
croutine (custom_rtc) #2
"""
self.test_same_entity_conflicting_scheme(mapping)
def test_complex_mapping_case(self, alt=None):
# Test a complex case where an object is mapped using
# one scheme, but a specific symbol in that object is mapped
# using another. Another object and symbol is mapped the other way around.
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .text ...) A, B
#
# flash_rodata
# *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .rodata ...) A, B
#
# dram0_data
# *(EXCLUDE_FILES(libfreertos.a:timers) .data ..) B
# *(.dram ...)
# *libfreertos.a:croutine(.rodata .rodata.*) C
# *libfreertos.a:timers(.rodata.prvProcessReceivedCommands ...) E
#
# dram0_bss
# *(EXCLUDE_FILE(libfreertos.a:timers) .bss .bss.* ...) B
# *(EXCLUDE_FILE(libfreertos.a:timers) COMMON) B
#
# iram0_text
# *(.iram ...)
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) C
# *libfreertos.a:timers(.literal .literal.prvProcessReceivedCommands ...) E
#
# rtc_text
# *(rtc.text .rtc.literal)
# libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) F
# libfreertos.a:timers (.text .text.prvCheckForValidListAndQueue ...) D.2
#
# rtc_data
# *(rtc.data)
# *(rtc.rodata)
# libfreertos.a:timers (.data .data.*) D
# libfreertos.a:timers (.rodata ...) D.2
#
# rtc_bss
# *(rtc.bss .rtc.bss)
# libfreertos.a:timers (.bss .bss.*) D
# libfreertos.a:timers (COMMON) D
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash) #1
timers (rtc) #2
timers:prvProcessReceivedCommands (noflash) #3
croutine:prvCheckPendingReadyList (rtc) #4
"""
self.add_fragments(alt if alt else mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
dram0_data = expected['dram0_data']
iram0_text = expected['iram0_text']
dram0_bss = expected['dram0_bss']
rtc_text = expected['rtc_text']
rtc_data = expected['rtc_data']
rtc_bss = expected['rtc_bss']
# Exclusions for #1 A
flash_text[0].exclusions.add(CROUTINE)
flash_rodata[0].exclusions.add(CROUTINE)
# Exclusions for #2 B
flash_text[0].exclusions.add(TIMERS)
flash_rodata[0].exclusions.add(TIMERS)
dram0_data[0].exclusions.add(TIMERS)
dram0_bss[0].exclusions.add(TIMERS)
dram0_bss[1].exclusions.add(TIMERS)
# Commands for #1 C
# List all relevant sections excluding #4 for text -> iram0_text
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
# Commands for #4 F
# Processed first due to alphabetical ordering
rtc_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
# Commands for #2 D
# List all relevant sections excluding #3 for text -> rtc_text and D.2
# rodata -> rtc_data
timers_sections = self.entities.get_sections('libfreertos.a', 'timers')
filtered_sections = fnmatch.filter(timers_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(timers_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvProcessReceivedCommands')]
filtered_sections.append('.text')
rtc_text.append(InputSectionDesc(TIMERS, set(filtered_sections), []))
rtc_data.append(InputSectionDesc(TIMERS, dram0_data[0].sections, []))
filtered_sections = fnmatch.filter(timers_sections, '.rodata.*')
filtered_sections = [s for s in filtered_sections if not s.endswith('prvProcessReceivedCommands')]
rtc_data.append(InputSectionDesc(TIMERS, set(filtered_sections), []))
rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[0].sections, []))
rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[1].sections, []))
# Commands for #3 E
iram0_text.append(
InputSectionDesc(
TIMERS, set(['.text.prvProcessReceivedCommands', '.literal.prvProcessReceivedCommands']), []
)
)
dram0_data.append(InputSectionDesc(TIMERS, set(['.rodata.prvProcessReceivedCommands']), []))
self.compare_rules(expected, actual)
def test_multiple_mapping_fragments(self):
# Test mapping multiple fragments succeeds, particularly
# generating exclusions from the default command of archive
# and object specificity.
#
# flash_text
# * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...)
#
# flash_rodata
# * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...)
#
# iram0_text
mapping = """
[mapping:test_1]
archive: libfreertos.a
entries:
croutine (noflash) #1
[mapping:test_2]
archive: libfreertos2.a
entries:
* (noflash) #2
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
# Exclusions for #1 A
flash_text[0].exclusions.add(CROUTINE)
flash_rodata[0].exclusions.add(CROUTINE)
# Exclusions for #1 & #2 B
flash_text[0].exclusions.add(FREERTOS2)
flash_rodata[0].exclusions.add(FREERTOS2)
# Command for #1 C
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
# Command for #1 & #2 D
iram0_text.append(InputSectionDesc(FREERTOS2, flash_text[0].sections, []))
dram0_data.append(InputSectionDesc(FREERTOS2, flash_rodata[0].sections, []))
self.compare_rules(expected, actual)
def test_mapping_same_lib_in_multiple_fragments_no_conflict(self):
# Test mapping fragments operating on the same archive.
# In these cases, the entries are taken together.
#
# Uses the same entries as C_05 but spreads them across
# two fragments. The output should still be the same.
mapping = """
[mapping:test_1]
archive: libfreertos.a
entries:
croutine (noflash) #1
timers:prvProcessReceivedCommands (noflash) #3
[mapping:test_2]
archive: libfreertos.a
entries:
timers (rtc) #2
croutine:prvCheckPendingReadyList (rtc) #4
"""
self.test_complex_mapping_case(mapping)
def test_mapping_same_lib_in_multiple_fragments_conflict(self):
# Test mapping fragments operating on the same archive
# with conflicting mappings.
mapping = """
[mapping:test_1]
archive: libfreertos.a
entries:
croutine (noflash) #1
[mapping:test_2]
archive: libfreertos.a
entries:
croutine (rtc) #2
"""
self.test_same_entity_conflicting_scheme(mapping)
def test_command_order(self):
# Test command order sorting: the commands should be sorted by specificity, then
# alphabetically. This contributes to deterministic output given
# the same input mapping entries.
#
# This ordering is also tested in other tests as a side-effect.
#
# flash_text
# * (EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:croutine2)) A
# libfreertos.a:croutine(.text ....) B
#
# iram0_text
#
# * (.iram .iram.*)
# libfreertos:croutine(.text .literal ...) C
# libfreertos:croutine(.text.prvCheckDelayedList .literal.prvCheckDelayedList) F
# libfreertos:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) G
# libfreertos2:croutine(.text .literal ...) D
# libfreertos2:croutine2(.text .literal ...) E
mapping = """
[mapping:freertos2]
archive: libfreertos2.a
entries:
croutine2 (noflash_text) #1
croutine (noflash_text) #2
[mapping:freertos]
archive: libfreertos.a
entries:
croutine:prvCheckPendingReadyList (noflash_text) #3
croutine:prvCheckDelayedList (noflash_text) #4
"""
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']
# Exclusions for #1 A
flash_text[0].exclusions.add(CROUTINE)
flash_text[0].exclusions.add(Entity(FREERTOS2.archive, 'croutine2'))
flash_text[0].exclusions.add(Entity(FREERTOS2.archive, 'croutine'))
# Intermediate command for #3 and #4 B
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckDelayedList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
# Command for
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), [])
)
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine'), flash_text[0].sections, []))
iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine2'), flash_text[0].sections, []))
self.compare_rules(expected, actual)
def test_ambigious_obj(self):
# Command generation for ambiguous entry should fail.
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
port:xPortGetTickRateHz (noflash) #1
"""
self.add_fragments(mapping)
with self.assertRaises(GenerationException):
self.generation.generate(self.entities, False)
def test_root_mapping_fragment_conflict(self):
# Test that root mapping fragments are also checked for
# conflicts.
#
# 'custom_scheme' entries conflict the 'default' scheme
# entries.
mapping = """
[scheme:custom_scheme]
entries:
flash_text -> iram0_text
[mapping:default2]
archive: *
entries:
* (custom_scheme)
"""
self.add_fragments(mapping)
with self.assertRaises(GenerationException):
self.generation.generate(self.entities, False)
def test_root_mapping_fragment_duplicate(self):
# Same root mappings have no effect.
#
# custom_scheme has the 'iram -> iram0_text' in common with
# default scheme
mapping = """
[sections:custom_section]
entries:
.custom_section
[scheme:custom_scheme]
entries:
iram -> iram0_text
custom_section -> iram0_text
[mapping:default2]
archive: *
entries:
* (custom_scheme)
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
# Generate default command A
# Since these are the same 'specificity', the commands
# are arranged alphabetically.
expected['iram0_text'].append(expected['iram0_text'][0])
expected['iram0_text'][0] = InputSectionDesc(ROOT, ['.custom_section'], [])
self.compare_rules(expected, actual)
class ConfigTest(GenerationTest):
# Test command generation with conditions
def _test_conditional_on_scheme(self, perf, alt=None):
# Test that proper commands are generated if using
# schemes with conditional entries.
scheme = """
[sections:cond_text_data]
entries:
if PERFORMANCE_LEVEL >= 1:
.text+
.literal+
else:
.rodata+
[scheme:cond_noflash]
entries:
if PERFORMANCE_LEVEL >= 1:
cond_text_data -> iram0_text
else:
cond_text_data -> dram0_data
"""
mapping = """
[mapping:test]
archive: lib.a
entries:
* (cond_noflash)
"""
self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf))
self.add_fragments(scheme)
self.add_fragments(alt if alt else mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
if perf >= 1:
flash_text = expected['flash_text']
iram0_text = expected['iram0_text']
flash_text[0].exclusions.add(Entity('lib.a'))
iram0_text.append(InputSectionDesc(Entity('lib.a'), flash_text[0].sections, []))
else:
flash_rodata = expected['flash_rodata']
dram0_data = expected['dram0_data']
flash_rodata[0].exclusions.add(Entity('lib.a'))
dram0_data.append(InputSectionDesc(Entity('lib.a'), flash_rodata[0].sections, []))
self.compare_rules(expected, actual)
def test_conditional_on_scheme_00(self):
self._test_conditional_on_scheme(0)
def test_conditional_on_scheme_01(self):
self._test_conditional_on_scheme(1)
def test_conditional_mapping(self, alt=None):
# Test that proper commands are generated
# in conditional mapping entries.
mapping = """
[mapping:default]
archive: *
entries:
* (default)
[mapping:test]
archive: lib.a
entries:
if PERFORMANCE_LEVEL = 1:
obj1 (noflash)
elif PERFORMANCE_LEVEL = 2:
obj1 (noflash)
obj2 (noflash)
elif PERFORMANCE_LEVEL = 3:
obj1 (noflash)
obj2 (noflash)
obj3 (noflash)
"""
for perf_level in range(0, 4):
self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf_level))
self.generation.mappings = {}
self.add_fragments(alt if alt else mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
if perf_level < 4 and perf_level > 0:
for append_no in range(1, perf_level + 1):
flash_text = expected['flash_text']
flash_rodata = expected['flash_rodata']
iram0_text = expected['iram0_text']
dram0_data = expected['dram0_data']
obj_str = 'obj' + str(append_no)
flash_text[0].exclusions.add(Entity('lib.a', obj_str))
flash_rodata[0].exclusions.add(Entity('lib.a', obj_str))
iram0_text.append(InputSectionDesc(Entity('lib.a', obj_str), flash_text[0].sections, []))
dram0_data.append(InputSectionDesc(Entity('lib.a', obj_str), flash_rodata[0].sections, []))
self.compare_rules(expected, actual)
def test_multiple_fragment_same_lib_conditional(self):
# Test conditional entries on new mapping fragment grammar.
# across multiple fragments.
mapping = """
[mapping:default]
archive: *
entries:
* (default)
[mapping:base]
archive: lib.a
entries:
if PERFORMANCE_LEVEL = 1:
obj1 (noflash)
elif PERFORMANCE_LEVEL = 2:
obj1 (noflash)
elif PERFORMANCE_LEVEL = 3:
obj1 (noflash)
[mapping:extra]
archive: lib.a
entries:
if PERFORMANCE_LEVEL = 1:
obj1 (noflash) # ignore duplicate definition
elif PERFORMANCE_LEVEL = 2:
obj2 (noflash)
elif PERFORMANCE_LEVEL = 3:
obj2 (noflash)
obj3 (noflash)
"""
self.test_conditional_mapping(mapping)
class FlagTest(GenerationTest):
# Test correct generation of mapping fragment entries
# with flags.
def test_flags_basics(self):
# Test that input section commands additions are done (KEEP SORT).
# Test that order dependent commands are properly generated (ALIGN, SURROUND)
# Normally, if an entry has the same mapping as parent, commands.
# are not emitted for them. However, if there are flags, they should be -
# only for the scheme entries that have flags, though.
# Flag entries split across multiple entries work.
#
# flash_text
# *((EXCLUDE_FILE(libfreertos:timers libfreertos:croutine).text ...) A
# KEEP(* (SORT_BY_NAME(EXCLUDE_FILE(libfreertos:timers).text) ...) B
#
# flash_rodata
# *((EXCLUDE_FILE(libfreertos:timers) .rodata ...) C
# _sym2_start D.1
# . = ALIGN(4) E.1
# KEEP(* (EXCLUDE_FILE(libfreertos:timers) .rodata ...) F
# _sym2_end D.2
# . = ALIGN(4) E.2
#
# iram0_text
# *(.iram .iram.*)
# . = ALIGN(4) G.1
# _sym1_start H.1
# libfreertos.a:croutine(.text .literal ...) I
# . = ALIGN(4) G.2
# _sym1_end H.2
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
croutine (noflash_text);
text->iram0_text ALIGN(4, pre, post) SURROUND(sym1) #1
timers (default);
text->flash_text KEEP() SORT(name) #2
timers (default);
rodata->flash_rodata SURROUND(sym2) ALIGN(4, pre, post) #3
"""
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']
flash_rodata = expected['flash_rodata']
# Exclusions in flash_text for timers and croutine A
flash_text[0].exclusions.add(CROUTINE)
flash_text[0].exclusions.add(TIMERS)
# Command for #3 B
flash_text.append(InputSectionDesc(TIMERS, flash_text[0].sections, [], keep=True, sort=('name', None)))
# Exclusions in flash_rodata for timers C
flash_rodata[0].exclusions.add(TIMERS)
# Commands for #3 D.1, E.1, F, D.2, E.2
flash_rodata.append(SymbolAtAddress('_sym2_start'))
flash_rodata.append(AlignAtAddress(4))
flash_rodata.append(InputSectionDesc(TIMERS, flash_rodata[0].sections, []))
flash_rodata.append(SymbolAtAddress('_sym2_end'))
flash_rodata.append(AlignAtAddress(4))
# Commands for # G.1, H.1, I, G.2, H.2
iram0_text.append(AlignAtAddress(4))
iram0_text.append(SymbolAtAddress('_sym1_start'))
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
iram0_text.append(AlignAtAddress(4))
iram0_text.append(SymbolAtAddress('_sym1_end'))
self.compare_rules(expected, actual)
def test_flags_intermediate_exclusion_command_root(self):
# Test that intermediate exclusion commands from root-level commands
# are included in the flags.
#
# flash_text
# _sym1_start A.1
# KEEP(* (EXCLUDE_FILE(libfreertos:croutine).text ...) B
# KEEP(libfreertos.a:croutine(...))) C
# _sym1_end A.2
#
# iram0_text
# *(.iram .iram.*)
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D
mapping = """
[mapping:default]
archive: *
entries:
# 1
* (default);
text->flash_text SURROUND(sym1) KEEP() #2
[mapping:test]
archive: libfreertos.a
entries:
croutine:prvCheckPendingReadyList (noflash_text) #3
"""
self.generation.mappings = {}
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']
# Command for #2, pre A.1
flash_text.insert(0, SymbolAtAddress('_sym1_start'))
# Command for #1 with KEEP() B
# and exclusion for #3
flash_text[1].keep = True
flash_text[1].exclusions.add(CROUTINE)
# Implicit exclusion command for #3 C
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
# Command for #2, post A.2
flash_text.append(SymbolAtAddress('_sym1_end'))
# Command for #3 D
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_flags_intermediate_exclusion_command_lib(self):
# Test that intermediate exclusion commands from lib-level commands
# are included in the flags.
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a).text ...)
# _sym1_start A.1
# KEEP(libfreertos.a(EXCLUDE_FILE(libfreertos:croutine).text.* ...)) B
# KEEP(libfreertos.a:croutine(...))) C
# _sym1_end A.2
#
# iram0_text
# *(.iram .iram.*)
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
# 1
* (default);
text->flash_text SURROUND(sym1) KEEP() #2
croutine:prvCheckPendingReadyList (noflash_text) #3
"""
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']
# Command for #2, pre A.1
flash_text.append(SymbolAtAddress('_sym1_start'))
flash_text[0].exclusions.add(FREERTOS)
# Command for #1 with KEEP() B
# and exclusion for #3
flash_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE], keep=True))
# Implicit exclusion command for #3 C
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
# Command for #2, post A.2
flash_text.append(SymbolAtAddress('_sym1_end'))
# Command for #3 C
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_flags_intermediate_exclusion_command_obj(self):
# Test that intermediate exclusion commands from obj-level commands
# are included in the flags.
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a).text ...)
# _sym1_start A.1
# KEEP(libfreertos.a:croutine(...))) B
# _sym1_end A.2
#
# iram0_text
# *(.iram .iram.*)
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) C
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
# 1
croutine (default);
text->flash_text SURROUND(sym1) KEEP() #2
croutine:prvCheckPendingReadyList (noflash_text) #3
"""
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']
# Command for #2, pre A.1
flash_text.append(SymbolAtAddress('_sym1_start'))
flash_text[0].exclusions.add(CROUTINE)
# Implicit exclusion command for #3 B
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
# Command for #2, post A.2
flash_text.append(SymbolAtAddress('_sym1_end'))
# Command for #3 C
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_flags_separate_exclusion_command_if_explicit_root(self):
# Explicit commands are separated from the parent's flags.
#
# flash_text
# _sym1_start A.1
# KEEP(* (EXCLUDE_FILE(libfreertos:croutine).text ...) B
# _sym1_end A.2
# KEEP(libfreertos.a:croutine(...))) C
#
# iram0_text
# *(.iram .iram.*)
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D
mapping = """
[mapping:default]
archive: *
entries:
# 1
* (default);
text->flash_text SURROUND(sym1) KEEP() #2
[mapping:test]
archive: libfreertos.a
entries:
croutine (default) #3
croutine:prvCheckPendingReadyList (noflash_text) #4
"""
self.generation.mappings = {}
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']
# Command for #2, pre A.1
flash_text.insert(0, SymbolAtAddress('_sym1_start'))
# Command for #1 with KEEP() B
# and exclusion for #3
flash_text[1].keep = True
flash_text[1].exclusions.add(CROUTINE)
# Command for #2, post A.2
flash_text.append(SymbolAtAddress('_sym1_end'))
# Command for #3 C
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
# Command for #4 D
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_flags_separate_exclusion_command_if_explicit_lib(self):
# Explicit commands are separated from the parent's flags.
#
# flash_text
# *(EXCLUDE_FILE(libfreertos.a).text ...)
# _sym1_start A.1
# KEEP(libfreertos.a(EXCLUDE_FILE(libfreertos:croutine).text.* ...)) B
# _sym1_end A.2
# KEEP(libfreertos.a:croutine(...))) C
#
# iram0_text
# *(.iram .iram.*)
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D
mapping = """
[mapping:test]
archive: libfreertos.a
entries:
# 1
* (default);
text->flash_text SURROUND(sym1) KEEP()
croutine (default) #2
croutine:prvCheckPendingReadyList (noflash_text) #3
"""
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']
# Command for #2, pre A.1
flash_text.append(SymbolAtAddress('_sym1_start'))
flash_text[0].exclusions.add(FREERTOS)
# Command for #1 with KEEP() B
# and exclusion for #3
flash_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE], keep=True))
# Command for #2, post A.2
flash_text.append(SymbolAtAddress('_sym1_end'))
# Implicit exclusion command for #3 C
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
filtered_sections.append('.text')
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
# Command for #3 C
iram0_text.append(
InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])
)
self.compare_rules(expected, actual)
def test_flag_additions(self):
# Test ability to add flags as long as no other mapping fragments
# does the same thing.
mapping = """
[mapping:default_add_flag]
archive: *
entries:
* (default);
text->flash_text KEEP()
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_text[0].keep = True
self.compare_rules(expected, actual)
def test_flags_flag_additions_duplicate(self):
# Test same flags added to same entity - these
# are ignored.
mapping = """
[mapping:default_add_flag_1]
archive: *
entries:
* (default);
text->flash_text KEEP()
[mapping:default_add_flag_2]
archive: *
entries:
* (default);
text->flash_text KEEP()
"""
self.add_fragments(mapping)
actual = self.generation.generate(self.entities, False)
expected = self.generate_default_rules()
flash_text = expected['flash_text']
flash_text[0].keep = True
self.compare_rules(expected, actual)
def test_flags_flag_additions_conflict(self):
# Test condition where multiple fragments specifies flags
# to same entity - should generate exception.
mapping = """
[mapping:default_add_flag_1]
archive: *
entries:
* (default);
text->flash_text ALIGN(2)
[mapping:default_add_flag_2]
archive: *
entries:
* (default);
text->flash_text SURROUND(sym1)
"""
self.add_fragments(mapping)
with self.assertRaises(GenerationException):
self.generation.generate(self.entities, False)
if __name__ == '__main__':
unittest.main()