NOTICE: The Processors Wiki will End-of-Life in December of 2020. It is recommended to download any files or other content you may need that are hosted on processors.wiki.ti.com. The site is now set to read only.
C6000 Memory models
- 1 Introduction
- 2 Background
- 3 Review Memory Models
- 4 Tradeoffs
- 5 Default: far_aggregates
- 6 Definition and Declaration Must Agree
- 7 Consistency of Builds
- 8 Rely on Compiler Defaults
- 9 Force Everything to far
- 10 Note on Linker Diagnostics
C6000 Memory Models & Vendor Libraries
This document discusses usage of the C6000 C compiler memory models from the perspective of an object library vendor. It assumes you are using the C6000 compiler v5.1.0 or higher. Note the v5.1.0 compiler comes bundled with Code Composer Studio 3.1.
Review Memory Models
Previous versions of the compiler offered memory models for managing both code and data. The code models were replaced by linker generated trampolines. The compiler always generates near calls, and lets the linker apply trampolines to calls that do not reach. Details on trampolines are in this linker application note.
The data memory models remain the same. But the options for choosing the data memory model, as well as the default, are different from previous releases.
|All data near||No|
|Aggregate data far; scalar data near||Yes|
|All data far||No|
Older compilers offered the options –ml [ 0-3 ] which controlled both code and data models. Those options are still supported, though deprecated. Please stop using those options as soon as possible. Note the options –ml [ 1-3 ] indicate use of the far code model, and thus effectively disable trampolines and all their attendant benefits.
The rest of this document only discusses data memory models.
A variable definition tells the compiler everything it needs to know to actually create a given variable …
<syntaxhighlight lang='c'> int global_array; // definition </syntaxhighlight>
A variable declaration tells the compiler the type and name of a variable, but typically omits details needed to create it...
<syntaxhighlight lang='c'> extern int global_array; // declaration </syntaxhighlight>
The keywords near and far can be used on either a variable definition or declaration.
<syntaxhighlight lang='c'> far extern int global_array; // far declaration
near static int i; // near definition </syntaxhighlight>
Use of near or far overrides any build option.
A variable access is a read or write of the variable …
<syntaxhighlight lang='c'> global_array[i] = 0; // access </syntaxhighlight>
A scalar is a simple variable. <syntaxhighlight lang='c'> int i; // define some
float f; // scalars
int *ptr; // pointer is scalar </syntaxhighlight>
An aggregate is an array, structure, or union. <syntaxhighlight lang='c'> int global_array; // define some
struct sss global_struct; // aggregates
union uuu global_union; </syntaxhighlight>
Definition & Access, Near & Far
This table shows whether a variable with a given definition can be accessed by the given method.
The key thing to notice is that a far access of a near variable works, while a near access of a far variable does not work.
Drawbacks of near
If near variables work so well, why not use them all the time?
The total size of all the near variables is limited to 32K bytes. That is because such variables are accessed at an offset from the register B14, and that offset is limited to 15-bits.
All the near variables, from the libraries and from user code, must be combined together into a single section named .bss and allocated to memory together. For various system related reasons, that is not always possible.
Costs: near vs. far Accesses
In contrast to software pipelined loops, performance and code size of variable accesses is directly linked. As performance improves, so does code size.
Intuition says that near variable access is cheaper than far variable access. That ’ s true, but not as often as you might think.
If a variable is accessed indirectly, then the cost is the same. Indirect access obviously includes any access involving a pointer.
- ptr_to_int = ...
ptr_to_struct->member = ... </syntaxhighlight>
It also includes any other computed memory address. <syntaxhighlight lang='c'> global_array[i] = ...
local_var = ...
function_argument = ... </syntaxhighlight>
Near access is cheaper only in the case of direct access by name (possibly plus a constant offset). <syntaxhighlight lang='c'> global_int = ...
global_struct.member = ...
global_array = ... </syntaxhighlight>
Thus, with regard to costs, the importance of the choice of memory model is linked to how often direct accesses occur. If direct accesses are rare, costs will vary little with the choice of memory model. If direct memory access is common, then near memory model will tend to be cheaper than far memory model.
The default memory model is far_aggregates because, in most code, direct memory access is common only among scalars. Aggregates are nearly always accessed indirectly.
Furthermore, placing only scalars in near memory means the 32K size limit will almost never be exceeded. The largest scalars are 8 bytes big, and it would take 4097 separately named 8-byte scalars to exceed the 32K limit.
Thus, the costs of direct access are minimized while avoiding the 32K size limit of near memory.
Definition and Declaration Must Agree
Variable definitions appear in source files, and variable declarations appear in header files. Declarations influence how accesses are performed. The near or far property of a given variable as written in the definition and the declaration must agree. Agreement, as used here, does not mean identical. It means there will be no error in execution at runtime. Identical use of near or far is one way to get agreement. But it is not the only one. As the table in section 5 indicates, the following would work...
<syntaxhighlight lang='c'> /* header file */ far extern int global_var; // far declaration
/* library source file */ near int global_var; // near definition </syntaxhighlight>
Note the converse is not true. A near declaration cannot access a far definition.
Explicit use of keywords is not the only way to obtain the results in the above example. The same thing happens if the library source file is built with the default far_aggregates data memory model, and the user code that includes the header file is built with far data memory model.
Consistency of Builds
A library is supplied with at least two components: the object files and the header files. The object files are built when the library is released. The header files are included in the user ’ s source and build. Thus, the header files, and the declarations they contain, are built with the user ’ s build options.
Some libraries are more sophisticated. They come with configuration tools that auto-generate additional source files. These source files can define variables which are accessed in library object code. The auto-generated source files are built with the user ’ s build options.
In all these cases, consistency of builds is required so that, as discussed in section 9, the variable definitions and declarations agree. A library vendor has two choices: document build option requirements, or write the header files and auto-generate source files such that they are independent of the user ’ s build options.
Documenting which build options should or should not be used is a reasonable choice. If the user makes an error, the linker will issue a warning or error message. And correction is straightforward: adopt the build options required by the library.
Rely on Compiler Defaults
This is the first of two sections that make specific recommendations. This section discusses using the compiler default option where scalars are near and aggregates are far.
- That's how the user is likely to do it
- Cost savings for scalar access
- No extra costs for most aggregate accesses (which are done indirectly)
- Easy to document
- Easy to justify
- Scenarios where user gets in trouble are fairly exotic
- Library scalars are allocated to user's .bss section. Must determine how much memory this is and document it.
- Some system configurations require that all the library data be allocated separately, i.e. not in .bss, and thus must be far
How User Could Get in Trouble
- User builds with near data memory model and does a direct access to an aggregate defined in the library
- User builds with far data memory model, including an auto-generated file that defines a scalar, and library code performs an access to that scalar
- All of these scenarios result in diagnostic messages from the linker
Force Everything to far
This section discusses a second recommendation. Explicitly define and declare every global variable to be far. Use of the far keyword in this manner overrides any memory model build options chosen by the user.
- The user cannot get in trouble
- No mixing of library variables with user .bss variables
- Library data can be allocated anywhere in memory
- Extra costs for every direct scalar access. This could prove pivotal if it occurs in critical path code.
The trade-off here is library costs versus ease of support. It is very easy to support a library where all of the data will always be far. You only need a bit of documentation to tell the user to expect a .far section. This .far section can be allocated anywhere from a correctness perspective. But for that ease of support, you may a price, even a high price, in lost performance and added code size. The only way to know for sure is to take measurements and see.
The compiler RTS library declares all data as far. Most of the RTS data is aggregates that are accessed indirectly. The few scalars that are accessed directly are in seldom used functions.
Note the RTS source actually uses _DATA_ACCESS as the storage class of all variables, and has <syntaxhighlight lang='c'>
- define _DATA_ACCESS far
</syntaxhighlight> in the header file linkage.h, which is in turn included by all the source files. Thus, it is easy to change this implementation detail. Note rebuilding the library is required. That is pretty straightforward in the case of the RTS library. It may not be straightforward for other libraries.
Note on Linker Diagnostics
The linker diagnostic emitted when a data memory model mistake occurs is a warning, not an error. Here is an example:
>> warning: Detected a near (.bss section relative) data reference to the symbol _global defined in section .far. The reference occurs in file2.obj, section .text, SPC offset 00000008. Either make the symbol near data by placing it in the .bss section, or make the references to the symbol far. For C/C++ code use 'far' or 'near' modifiers on the type definition of the symbol or compile with the --mem_model:data switch.
This is a warning because the linker cannot be certain it is an error. If the linker could somehow be certain that the error occurred in compiler generated code, it would generate an error instead. It cannot do that. And it is possible, in assembly language, to employ a custom paging scheme that uses B14 as the base register. In such a case, the error message is spurious. Thus, it is categorized as a warning, and not an error.