7 min read

TASKING Section Concatenation

The TASKING compiler toolchain [1] has a very useful feature. It automatically defines _lc_ “linker labels” for variables. See section 7.10 of the User Guide. What do they do? They mark the assigned memory locations of a section’s start and end. Given a variable x, the linker creates _lc_ub_x and _lc_ue_x as symbols that a C or C++ program can declare as external in order to access the variable’s beginning and end location in a firmware’s memory space. The “end” is the location after the variable’s last location, of course. The difference between the two locations gives the size of the memory allocated to the variable.

However, there is a catch.

The Catch

The linker does concatenate sections with the same name. If you declare multiple variables in the same section, the linker will combine them into a single section. Good, but the linker labels are not updated to reflect the section’s new size. The end address of the section is still the same as it was when created by the first variable, prior to concatenation, which means that the end address of the section is incorrect. This can lead to problems when trying to access a block of variables in some given section, as the end address will only match the end of the first variable in the section, not the end of the entire section.

Demonstrating the Catch

The following C extract demonstrates the problem. It defines three ROM-based integer variables in the same var section. The “used, protect” attributes prevent the compiler and linker from discarding the variables as unused.

#define VAR __attribute__((section("var"), used, protect))

const int var1 VAR = 1;
const int var2 VAR = 2;
const int var3 VAR = 3;

Compile and link in Release mode. This is an extract from the resulting linker-generated map file. Notice what the linker does with the variables.

Address Symbol
0x80002228 var3
0x8000222c _lc_ub_var
0x8000222c var1
0x80002230 _lc_ue_var
0x80002230 var2

See the problem! The _lc_ue_var label should be \(80002230_{16}+4=80002234_{16}\) in order to include var2; also, the _lc_ub_var label should be \(8000228_{16}\).

Sum the Section

Just to emphasise, define a sum_var function that sums the var section’s integers. See below. The function correctly reads the first variable, var1, but it will not read the second and third variables because the section’s begin and end addresses do not align with the concatenated section’s beginning and end.

int sum_var(void) {
    int sum = 0;
    extern const int _lc_ub_var[], _lc_ue_var[];
    for (const int *var_ptr = _lc_ub_var; var_ptr < _lc_ue_var; var_ptr++) {
        sum += *var_ptr;
    }
    return sum;
}

The sum is \(1\), not \(1+2+3=6\) as expected. What is happening under the bonnet?

Hard to say exactly without access to the toolchain code, but it looks like the linker assigns the initial begin and end labels at the first section declaration and does not reassign them when the section grows. The linking phases appear to set up the _lc_ symbols before collecting and concatenating their common section. Note that the GNU compiler toolchain does not have this problem—it correctly updates the start and stop symbols to reflect the concatenated section.

How to solve this for the TASKING toolchain?

Grouping Fix

This is one strategy: group the sections. The beginning and end become the group’s beginning and ending. Section grouping is a TASKING linker feature that allows you to group multiple sections into a single unit. By defining a group for the var section, the build ensures that the linker correctly computes the end address for the entire group, including all variables. The linker labels for the group will reflect the combined size of all the variables in the group, thus providing the correct addresses for the entire section.

Add this to the linker script and use the _lc_g labels.

group var(ordered, align = 4)
{
    select "var";
}

The map file now lists the following.

Address Symbol
0x80002160 _lc_gb_var
0x80002160 _lc_ub_var
0x80002160 var1
0x80002164 _lc_ue_var
0x80002164 var2
0x80002168 var3
0x80002178 _lc_ge_var

The linker orders and collects the variables in a group, then automatically assigns the group’s _lc_g begin and end label addresses to the allocated memory span of the group. Problem solved?

Well, not ideally. Explicitly defining linker groups for all variables is a bit of a pain. It works, but quite inconvenient if the firmware has many such groups.

Assembler Concatenation

The assembler has a concat attribute for section declarations. This attribute tells the linker to concatenate all sections with the same name into a single section. By using inline assembly, we can apply the concat attribute to the section declaration, ensuring that all variables declared in that section are concatenated correctly and that the linker computes the correct begin and end addresses for the entire section.

The second solution is therefore to apply the concat attribute to the section declaration using __asm directives in C to generate “concat” section content.

#define CONST_SECTION_CONCAT_DIRECTIVE(_section_, _directive_, _expression_) \
  __asm(".sdecl\t'" #_section_ "',data,rom,concat,protect\n\
\t.sect\t'" #_section_ "'\n\
\t." #_directive_ "\t" #_expression_)

This is how to use it:

/*
 * Define three word-sized variables in the 'var' section, initialised to 1, 2,
 * and 3 respectively. The .sdecl directive declares a section named 'var' with
 * specific attributes (data, rom, concat, protect). The .sect directive
 * switches to the 'var' section, and the .word directive initialises the
 * variable with the specified value. The CONST_SECTION_CONCAT_DIRECTIVE macro
 * is used to generate the necessary assembly code for each variable, ensuring
 * they are placed in the correct section with the appropriate attributes.
 */
CONST_SECTION_CONCAT_DIRECTIVE(var, word, 1);
CONST_SECTION_CONCAT_DIRECTIVE(var, word, 2);
CONST_SECTION_CONCAT_DIRECTIVE(var, word, 3);

/*
 * Directives for populating a section with different types of constants include:
 * - byte: for 8-bit values
 * - half: for 16-bit values
 * - word: for 32-bit values
 * - sfract: for signed 16-bit fractional values
 * - fract: for signed 32-bit fractional values
 * - float: for single-precision 32-bit floating-point values
 * - double: for double-precision 64-bit floating-point values
 * - asciiz: for null-terminated strings
 */
CONST_SECTION_CONCAT_DIRECTIVE(short_fractions, sfract, 0.5);
CONST_SECTION_CONCAT_DIRECTIVE(null_terminated, asciiz, "hello");

Note that macro applies stringification to the section name, directive, and expression, allowing it to generate the correct assembly code for each definition; the expression is pasted directly into the assembly language source. The concat attribute ensures that all variables declared in the same section are concatenated, and the linker computes the correct begin and end addresses for the entire section.

Conclusions

Which strategy is best? The grouping strategy is simpler to implement, but it can be cumbersome when there are many sections to group. Good for small projects. The assembler concatenation strategy is more flexible and provides finer-grained control over section attributes. Here is a decision matrix to help you decide which strategy to use:

Strategy Pros Cons
Grouping Simple to implement, no need for inline assembly Cumbersome for many sections, with less control over attributes
Assembler Concatenation More flexible, allows for fine-grained control over attributes Requires inline assembly, more complex to implement

You might ask, “Why do that?” Good question.

It allows the developer to control the order of variables in memory, resolving the allocated region at link time rather than run time. The firmware can load a section with prescribed content, such as a lookup table, and the linker will compute the correct begin and end addresses for the section, which the firmware can then use to access its content. The firmware can access the section’s correct location, its starting point and its terminating point. This ability has numerous practical applications.

[1]
TASKING, TriCore C compiler user guide. 2023. Available: https://www.tasking.com/support/tricore/ctc_user_guide_v6.3r1.pdf