6. Placing code and data¶
When compiling for modern computers, code and data placement often requires minimal attention, as tools handle it effectively for same-machine targets.
Cross-compilation is more complex, requiring knowledge of the target system. Embedded systems are even more challenging, with widely varying memory systems necessitating precise control over code and data placement.
Effective embedded system tools provide control over code and data placement. This is achieved using building blocks that offer placement flexibility and control. The compiler assists by providing abstractions through type qualifiers, initializers, and runtime model settings, which simplify configuring the memory system.
6.1. Sections¶
A section is a unit of code or data. The compiler will place code and data into different sections which are saved as separate sections in the object file.
The compiler places data objects and functions into suitable sections
following specific rules. For data objects, it considers if the object is
const, its address space, and explicit initializers.
Section name |
Type |
Memory kind |
Description |
|---|---|---|---|
|
text |
ROM |
Executable code |
|
bss |
RAM |
Zero-initialized (bss) data |
|
data |
RAM |
Initialized data |
|
rodata |
ROM |
Constant data |
|
bss |
RAM |
Zero-initialized (bss) zero page data |
|
data |
RAM |
Initialized zero page data |
|
rodata |
ROM |
Switch tables |
|
rodata |
ROM |
Data initializers for
|
|
rodata |
ROM |
Data initializers for
|
|
rodata |
ROM |
Data initializer table |
|
noinit |
RAM |
Pseudo registers in zero page |
|
text |
ROM |
Reset vector, when used |
|
noinit |
RAM |
Heap memory, for
|
|
noinit |
RAM |
CPU stack, always in page 1 on the 6502 |
|
noinit |
RAM |
C stack, simulated stack, anywhere in memory |
|
noinit |
RAM |
Commodore targets, save
area for |
|
bss |
RAM |
Zero-initialized (bss) far data, MEGA65 only |
|
data |
RAM |
Initialized far data, and MEGA65 only |
|
rodata |
ROM |
Constant far data, MEGA65 only |
|
rodata |
ROM |
Data initializers for
|
|
bss |
RAM |
Zero-initialized (bss) huge data, MEGA65 only |
|
data |
RAM |
Initialized huge data, MEGA65 only |
|
rodata |
ROM |
Constant huge data, MEGA65 only |
|
rodata |
ROM |
Data initializers for
|
The sections izpage, idata, ifar, ihuge, and data_init_table
in the table above are linker generated.
Note
It is assumed that no ROM exists in the zero page. Consequently,
constants placed in the zero page are placed in zpage (the normal
RAM initialized zero page section), and the const attribute only
affects the object type.
Note
The table assigns sections to ROM and RAM for ROM-based applications that start on power-up. For hosted systems loading from storage to RAM, this distinction is not applicable. Always consider ROM-marked sections as read-only.
Section types are derived from the UNIX world; see Section kinds in the assembler reference for details.
Note
When generating a ROM-executable application, the linker separates an
initialized data section into two. Initializers go into a new i-prefixed
section (placed in ROM); the data section is placed in RAM. The C runtime
startup module copies initializers from ROM to RAM before giving control to
the main() function.
Linking process¶
The linker reads object files with sections and a placement rules file. This file describes available memory areas and their permitted sections, optionally with additional placement constraints.
Memories containing actual value bits (program code or initialized data) are emitted in the executable.
An executable can serve as input for ROM or FLASH programmers, be loaded by the target machine, or be used by a debugger that connects to or simulates the target. Various executable formats are available to suit different execution environments.
Note
Although code and data are entirely separated into different sections, placing these sections together in the same memory may be desirable. This can be achieved at the link stage.
Placing sections in memory¶
The linker rules file uses file extension .scm, which is actually
a source file in the Scheme language. However, you do not need to
learn Scheme in order to use it, as it just a simple description of
available memories and their associated properties. Sections are then
bound to the memories. You can also define the size of the stack and
heap in this file.
A simple example of such linker rules file is:
(define memories
'((memory program (address (#x801 . #xbfff)) (type ROM))
(memory data (address (#xc000 . #xcfff)) (type RAM))
))
The Scheme interpreter evaluates the rules file, which defines a single
global memories object. This object lists memory and block
objects. Sections can be bound to memories either by specifying the memory
type or by listing the sections within each memory.
The example simplifies memory system description. The linker deduces where compiler and linker-generated sections are placed; this requires specifying the memory type. To prevent automatic section placement in a memory, omit its type specification.
Note
You can specify a memory type and manually place custom sections. This is useful for sections requiring specific placement.
Note
Since the linker rules file is a Scheme program, it can theoretically
construct the memories object via functions or macros. After evaluation,
the Scheme interpreter is expected to leave a memories object in its
global environment. However, a simple file like the example is usually
sufficient.
6.2. Data initialization¶
A startup routine is performed before the main() function is called. This
startup routine takes care of preparing the execution environment for the C
program by initializing the stack pointer, initialize global variables
and prepare system resources such as file streams and the heap, if
used by the application.
Static data¶
Static data that needs to be initialized comes in two forms, zero initialized and those that have non-zero initializers. Zero initialization is done by filling such memory ranges with zero. Explicit initialization is done by copying an initializer section in ROM to its corresponding RAM area.
This process is table driven using a section named data_init_table
which should be placed in the same memory as the cdata section, which
holds constants.
Note
In a hosted environment, the linker provides the --hosted command-line
option. This assumes the program image loads directly into RAM from
external media. Variable initialization is done in-place via the
load action; therefore, separate initializers are not required.
The --target option also has the effect of enabling running in a hosted environment.
6.3. Stack¶
The 6502 has a small 256-byte built-in stack for saving return
addresses and registers. C variables are managed on a separate stack in
the larger default memory, with arbitrary size up to 64K. Its stack pointer
is a pseudo-register named _Vsp in the zero page.
These stacks help track the current execution state and enable the implementation of reentrant and recursive functions.
Stack size considerations¶
The stack sizes are defined in the linker rules file. The stack size should be set low as possible, but not any lower. If given too much space you are wasting RAM space not being used for anything. However, if set too small it will cause corruption of other data.
Exact stack use can be hard to predict and it is better to err on the side of some safety. Also during development of your application you may want to use a bit oversized stack if possible, to allow you to focus on other issues. In the end, you are most likely constrained by some fixed amount of RAM and need to balance how much you can use for the stack with the optional heap and statically allocated data.