esp-idf/tools/ldgen/test/test_generation.py
Frantisek Hrbata a08995b302 fix(ldgen): don't emit intermediate placements without sections
When a symbol needs to be placed to a different target than the one
designated for the object file, the object file is expanded, which
includes the following steps:

1. Creating a new placement for the symbol's input section with the
   specified target.
2. Excluding the object placement from the orignal target.
3. Creating a new intermediate placement for the object for the original
   target, where its input sections are expanded, excluding the input
   section for the symbol.

Let's illustrate the object expansion process with the following example:

[sections:rodata]
entries:
    .rodata+
    .sdata2+
    .srodata+

[scheme:default]
entries:
    text -> flash_text
    rodata -> flash_rodata

[scheme:noflash]
entries:
    text -> iram0_text
    rodata -> dram0_data

[mapping:soc_pm]
archive: libsoc.a
entries:
    gpio_periph: GPIO_HOLD_MASK (noflash)

gpio_periph section headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000000 00  AX  0   0  2
  [ 2] .data             PROGBITS        00000000 000034 000000 00  WA  0   0  1
  [ 3] .bss              NOBITS          00000000 000034 000000 00  WA  0   0  1
  [ 4] .rodata.GPIO_HOLD_MASK PROGBITS        00000000 000034 000058 00   A  0   0  4
  [ 5] .rodata.GPIO_PIN_MUX_REG PROGBITS        00000000 00008c 000058 00   A  0   0  4
  [ 6] .debug_info       PROGBITS        00000000 0000e4 0000d8 00      0   0  1
  [ 7] .rela.debug_info  RELA            00000000 0009d4 000108 0c   I 16   6  4
  [ 8] .debug_abbrev     PROGBITS        00000000 0001bc 000070 00      0   0  1
  [ 9] .debug_aranges    PROGBITS        00000000 00022c 000018 00      0   0  1
  [10] .rela.debug_aranges RELA            00000000 000adc 00000c 0c   I 16   9  4
  [11] .debug_line       PROGBITS        00000000 000244 0001ab 00      0   0  1
  [12] .debug_str        PROGBITS        00000000 0003ef 00022d 01  MS  0   0  1
  [13] .comment          PROGBITS        00000000 00061c 000030 01  MS  0   0  1
  [14] .note.GNU-stack   PROGBITS        00000000 00064c 000000 00      0   0  1
  [15] .riscv.attributes RISCV_ATTRIBUTES 00000000 00064c 000044 00      0   0  1
  [16] .symtab           SYMTAB          00000000 000690 000260 10     17  36  4
  [17] .strtab           STRTAB          00000000 0008f0 0000e1 00      0   0  1
  [18] .shstrtab         STRTAB          00000000 000ae8 0000d1 00      0   0  1

1. Creating a new placement
.dram0.data :
{
    *libsoc.a:gpio_periph.*(.rodata.GPIO_HOLD_MASK .sdata2.GPIO_HOLD_MASK .srodata.GPIO_HOLD_MASK)
}

2. Excluding the object placement
.flash.rodata :
{
    *(EXCLUDE_FILE(*libsoc.a:gpio_periph.*) .rodata.* ...)
}

3. Creating a new intermediate placement
.flash.rodata :
{
    *libsoc.a:gpio_periph.*(.rodata.GPIO_PIN_MUX_REG)
}

Now, let's do the same, but also move GPIO_PIN_MUX_REG to noflash with an updated mapping.

[mapping:soc_pm]
archive: libsoc.a
entries:
    gpio_periph: GPIO_HOLD_MASK (noflash)
    gpio_periph: GPIO_PIN_MUX_REG (noflash)

1. Creating a new placement
.dram0.data :
{
    *libsoc.a:gpio_periph.*(.rodata.GPIO_HOLD_MASK .sdata2.GPIO_HOLD_MASK .srodata.GPIO_HOLD_MASK)
    *libsoc.a:gpio_periph.*(.rodata.GPIO_PIN_MUX_REG .sdata2.GPIO_PIN_MUX_REG
                            .srodata.GPIO_PIN_MUX_REG)
}

2. Excluding the object placement
.flash.rodata :
{
    *(EXCLUDE_FILE(*libsoc.a:gpio_periph.*) .rodata.* ...)
}

3. Creating a new intermediate placement
.flash.rodata :
{
    *libsoc.a:gpio_periph.*
}

The *libsoc.a:gpio_periph.* entity in step 3 no longer has input
sections, as there are no remaining .rodata input sections in the object
file. The linker behavior for this mapping is to include all object
input sections that have not yet been placed as described in
https://sourceware.org/binutils/docs/ld.html#Input-Section-Basics
"If you use a file name without a list of sections, then all sections in
the input file will be included in the output section. This is not
commonly done, but it may by useful on occasion."

The map file for such mapping now contains following input sections

 .flash.rodata   0x3c0a0120    0x19b34
     *libsoc.a:gpio_periph.*()
     .debug_info    0x3c0b95bf       0xd8 esp-idf/soc/libsoc.a(gpio_periph.c.obj)
     .debug_abbrev  0x3c0b9697       0x70 esp-idf/soc/libsoc.a(gpio_periph.c.obj)
     .debug_aranges
                    0x3c0b9707       0x18 esp-idf/soc/libsoc.a(gpio_periph.c.obj)
     .debug_line    0x3c0b971f      0x1ab esp-idf/soc/libsoc.a(gpio_periph.c.obj)
     .debug_str     0x3c0b98ca      0x21a esp-idf/soc/libsoc.a(gpio_periph.c.obj)
                                    0x22d (size before relaxing)
     .comment       0x3c0b9ae4       0x30 esp-idf/soc/libsoc.a(gpio_periph.c.obj)
     .note.GNU-stack
                    0x3c0b9ae4        0x0 esp-idf/soc/libsoc.a(gpio_periph.c.obj)
     .riscv.attributes
                    0x3c0b9ae4       0x44 esp-idf/soc/libsoc.a(gpio_periph.c.obj)

This is incorrect, and such intermediate placement should not be
generated. This type of placement can be recognized because it is not
explicitly defined in the mapping and lacks input sections. We can
identify this in the significant function and prevent issuing commands
for such placement.

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
2025-01-21 20:37:58 +08:00

1772 lines
72 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, EntityDB
from ldgen.fragments import parse_fragment_file
from ldgen.generation import Generation, GenerationException
from ldgen.linker_script import LinkerScript
from ldgen.output_commands import AlignAtAddress, InputSectionDesc, SymbolAtAddress
from ldgen.sdkconfig import SDKConfig
except ImportError:
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from ldgen.entity import Entity, EntityDB
from ldgen.fragments import parse_fragment_file
from ldgen.generation import Generation, GenerationException
from ldgen.linker_script import LinkerScript
from ldgen.output_commands import AlignAtAddress, InputSectionDesc, 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')
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/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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[mapping:test]
archive: libfreertos.a
entries:
croutine (default) #1
croutine:prvCheckPendingReadyList (default) #2
"""
self.add_fragments(mapping)
self.test_rule_generation_default()
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 = u"""
[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 = u"""
[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 = u"""
[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_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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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 = u"""
[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()