Skip to content

Commit 4d494b7

Browse files
committed
doc: clarify STACKSZ and HEAPSZ behavior in evalsoc linker layout
Document how the default evalsoc linker scripts arrange heap and stack from opposite ends of RAM, and how SMP reserves one fixed stack slot per hart from the shared stack area. Clarify that HEAPSZ only controls the minimum heap reservation in the linker layout. The effective runtime heap used by malloc is bounded by __heap_start and __heap_end, so increasing HEAPSZ alone does not always increase the actual heap available at runtime. Also add matching comments in evalsoc GCC linker scripts and record the documentation clarification in changelog.rst. Signed-off-by: Huaqi Fang <578567190@qq.com>
1 parent be00a61 commit 4d494b7

7 files changed

Lines changed: 124 additions & 1 deletion

File tree

SoC/evalsoc/Board/nuclei_fpga_eval/Source/GCC/gcc_evalsoc_ddr.ld

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ REGION_ALIAS("DATA_LMA", ram)
4242
SECTIONS
4343
{
4444
/* To provide symbol __STACK_SIZE, __HEAP_SIZE and __SMP_CPU_CNT */
45+
/* __STACK_SIZE is the reserved stack size for each hart. */
4546
PROVIDE(__STACK_SIZE = 2K);
47+
/* __HEAP_SIZE is the minimum heap space reserved in linker layout. */
4648
PROVIDE(__HEAP_SIZE = 2K);
4749
PROVIDE(__SMP_CPU_CNT = 1);
4850
__TOT_STACK_SIZE = __STACK_SIZE * __SMP_CPU_CNT;
@@ -208,11 +210,18 @@ SECTIONS
208210
* 1. heap need to be align at 16 bytes
209211
* 2. __heap_start and __heap_end symbol need to be defined
210212
* 3. reserved at least __HEAP_SIZE space for heap
213+
*
214+
* Note:
215+
* - __HEAP_SIZE defines the minimum reserved heap size in linker layout
216+
* - the C runtime heap implementation uses the range [__heap_start, __heap_end)
217+
* - therefore actual heap available at runtime can be larger than __HEAP_SIZE
218+
* when there is extra free RAM between .bss/.heap and the stack region
211219
*/
212220
.heap (NOLOAD) : ALIGN(16)
213221
{
214222
. = ALIGN(16);
215223
PROVIDE( __heap_start = . );
224+
/* Reserve at least __HEAP_SIZE bytes for heap from low address upward. */
216225
. += __HEAP_SIZE;
217226
. = ALIGN(16);
218227
PROVIDE( __heap_limit = . );
@@ -221,11 +230,15 @@ SECTIONS
221230
.stack ORIGIN(RAM) + LENGTH(RAM) - __TOT_STACK_SIZE (NOLOAD) :
222231
{
223232
. = ALIGN(16);
233+
/* Heap can grow up to the start of the reserved stack area. */
224234
PROVIDE( _heap_end = . );
225235
PROVIDE( __heap_end = . );
226236
PROVIDE( __stack_start__ = . );
227237
PROVIDE( __StackLimit = . );
228238
PROVIDE( __StackBottom = . );
239+
/* Reserve per-hart stack slots at the top of RAM. In SMP, overflow may
240+
* corrupt a neighboring hart stack.
241+
*/
229242
. += __TOT_STACK_SIZE;
230243
. = ALIGN(16);
231244
PROVIDE( __StackTop = . );

SoC/evalsoc/Board/nuclei_fpga_eval/Source/GCC/gcc_evalsoc_flash.ld

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ REGION_ALIAS("RAM", ram)
4141
SECTIONS
4242
{
4343
/* To provide symbol __STACK_SIZE, __HEAP_SIZE and __SMP_CPU_CNT */
44+
/* __STACK_SIZE is the reserved stack size for each hart. */
4445
PROVIDE(__STACK_SIZE = 2K);
46+
/* __HEAP_SIZE is the minimum heap space reserved in linker layout. */
4547
PROVIDE(__HEAP_SIZE = 2K);
4648
PROVIDE(__SMP_CPU_CNT = 1);
4749
__TOT_STACK_SIZE = __STACK_SIZE * __SMP_CPU_CNT;
@@ -206,11 +208,18 @@ SECTIONS
206208
* 1. heap need to be align at 16 bytes
207209
* 2. __heap_start and __heap_end symbol need to be defined
208210
* 3. reserved at least __HEAP_SIZE space for heap
211+
*
212+
* Note:
213+
* - __HEAP_SIZE defines the minimum reserved heap size in linker layout
214+
* - the C runtime heap implementation uses the range [__heap_start, __heap_end)
215+
* - therefore actual heap available at runtime can be larger than __HEAP_SIZE
216+
* when there is extra free RAM between .bss/.heap and the stack region
209217
*/
210218
.heap (NOLOAD) : ALIGN(16)
211219
{
212220
. = ALIGN(16);
213221
PROVIDE( __heap_start = . );
222+
/* Reserve at least __HEAP_SIZE bytes for heap from low address upward. */
214223
. += __HEAP_SIZE;
215224
. = ALIGN(16);
216225
PROVIDE( __heap_limit = . );
@@ -219,11 +228,15 @@ SECTIONS
219228
.stack ORIGIN(RAM) + LENGTH(RAM) - __TOT_STACK_SIZE (NOLOAD) :
220229
{
221230
. = ALIGN(16);
231+
/* Heap can grow up to the start of the reserved stack area. */
222232
PROVIDE( _heap_end = . );
223233
PROVIDE( __heap_end = . );
224234
PROVIDE( __stack_start__ = . );
225235
PROVIDE( __StackLimit = . );
226236
PROVIDE( __StackBottom = . );
237+
/* Reserve per-hart stack slots at the top of RAM. In SMP, overflow may
238+
* corrupt a neighboring hart stack.
239+
*/
227240
. += __TOT_STACK_SIZE;
228241
. = ALIGN(16);
229242
PROVIDE( __StackTop = . );

SoC/evalsoc/Board/nuclei_fpga_eval/Source/GCC/gcc_evalsoc_flashxip.ld

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ REGION_ALIAS("RAM", ram)
4040
SECTIONS
4141
{
4242
/* To provide symbol __STACK_SIZE, __HEAP_SIZE and __SMP_CPU_CNT */
43+
/* __STACK_SIZE is the reserved stack size for each hart. */
4344
PROVIDE(__STACK_SIZE = 2K);
45+
/* __HEAP_SIZE is the minimum heap space reserved in linker layout. */
4446
PROVIDE(__HEAP_SIZE = 2K);
4547
PROVIDE(__SMP_CPU_CNT = 1);
4648
__TOT_STACK_SIZE = __STACK_SIZE * __SMP_CPU_CNT;
@@ -208,11 +210,18 @@ SECTIONS
208210
* 1. heap need to be align at 16 bytes
209211
* 2. __heap_start and __heap_end symbol need to be defined
210212
* 3. reserved at least __HEAP_SIZE space for heap
213+
*
214+
* Note:
215+
* - __HEAP_SIZE defines the minimum reserved heap size in linker layout
216+
* - the C runtime heap implementation uses the range [__heap_start, __heap_end)
217+
* - therefore actual heap available at runtime can be larger than __HEAP_SIZE
218+
* when there is extra free RAM between .bss/.heap and the stack region
211219
*/
212220
.heap (NOLOAD) : ALIGN(16)
213221
{
214222
. = ALIGN(16);
215223
PROVIDE( __heap_start = . );
224+
/* Reserve at least __HEAP_SIZE bytes for heap from low address upward. */
216225
. += __HEAP_SIZE;
217226
. = ALIGN(16);
218227
PROVIDE( __heap_limit = . );
@@ -221,11 +230,15 @@ SECTIONS
221230
.stack ORIGIN(RAM) + LENGTH(RAM) - __TOT_STACK_SIZE (NOLOAD) :
222231
{
223232
. = ALIGN(16);
233+
/* Heap can grow up to the start of the reserved stack area. */
224234
PROVIDE( _heap_end = . );
225235
PROVIDE( __heap_end = . );
226236
PROVIDE( __stack_start__ = . );
227237
PROVIDE( __StackLimit = . );
228238
PROVIDE( __StackBottom = . );
239+
/* Reserve per-hart stack slots at the top of RAM. In SMP, overflow may
240+
* corrupt a neighboring hart stack.
241+
*/
229242
. += __TOT_STACK_SIZE;
230243
. = ALIGN(16);
231244
PROVIDE( __StackTop = . );

SoC/evalsoc/Board/nuclei_fpga_eval/Source/GCC/gcc_evalsoc_ilm.ld

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ REGION_ALIAS("DATA_LMA", ram)
4040
SECTIONS
4141
{
4242
/* To provide symbol __STACK_SIZE, __HEAP_SIZE and __SMP_CPU_CNT */
43+
/* __STACK_SIZE is the reserved stack size for each hart. */
4344
PROVIDE(__STACK_SIZE = 2K);
45+
/* __HEAP_SIZE is the minimum heap space reserved in linker layout. */
4446
PROVIDE(__HEAP_SIZE = 2K);
4547
PROVIDE(__SMP_CPU_CNT = 1);
4648
__TOT_STACK_SIZE = __STACK_SIZE * __SMP_CPU_CNT;
@@ -206,11 +208,18 @@ SECTIONS
206208
* 1. heap need to be align at 16 bytes
207209
* 2. __heap_start and __heap_end symbol need to be defined
208210
* 3. reserved at least __HEAP_SIZE space for heap
211+
*
212+
* Note:
213+
* - __HEAP_SIZE defines the minimum reserved heap size in linker layout
214+
* - the C runtime heap implementation uses the range [__heap_start, __heap_end)
215+
* - therefore actual heap available at runtime can be larger than __HEAP_SIZE
216+
* when there is extra free RAM between .bss/.heap and the stack region
209217
*/
210218
.heap (NOLOAD) : ALIGN(16)
211219
{
212220
. = ALIGN(16);
213221
PROVIDE( __heap_start = . );
222+
/* Reserve at least __HEAP_SIZE bytes for heap from low address upward. */
214223
. += __HEAP_SIZE;
215224
. = ALIGN(16);
216225
PROVIDE( __heap_limit = . );
@@ -219,11 +228,15 @@ SECTIONS
219228
.stack ORIGIN(RAM) + LENGTH(RAM) - __TOT_STACK_SIZE (NOLOAD) :
220229
{
221230
. = ALIGN(16);
231+
/* Heap can grow up to the start of the reserved stack area. */
222232
PROVIDE( _heap_end = . );
223233
PROVIDE( __heap_end = . );
224234
PROVIDE( __stack_start__ = . );
225235
PROVIDE( __StackLimit = . );
226236
PROVIDE( __StackBottom = . );
237+
/* Reserve per-hart stack slots at the top of RAM. In SMP, overflow may
238+
* corrupt a neighboring hart stack.
239+
*/
227240
. += __TOT_STACK_SIZE;
228241
. = ALIGN(16);
229242
PROVIDE( __StackTop = . );

SoC/evalsoc/Board/nuclei_fpga_eval/Source/GCC/gcc_evalsoc_sram.ld

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ REGION_ALIAS("DATA_LMA", ram)
4343
SECTIONS
4444
{
4545
/* To provide symbol __STACK_SIZE, __HEAP_SIZE and __SMP_CPU_CNT */
46+
/* __STACK_SIZE is the reserved stack size for each hart. */
4647
PROVIDE(__STACK_SIZE = 2K);
48+
/* __HEAP_SIZE is the minimum heap space reserved in linker layout. */
4749
PROVIDE(__HEAP_SIZE = 2K);
4850
PROVIDE(__SMP_CPU_CNT = 1);
4951
__TOT_STACK_SIZE = __STACK_SIZE * __SMP_CPU_CNT;
@@ -209,11 +211,18 @@ SECTIONS
209211
* 1. heap need to be align at 16 bytes
210212
* 2. __heap_start and __heap_end symbol need to be defined
211213
* 3. reserved at least __HEAP_SIZE space for heap
214+
*
215+
* Note:
216+
* - __HEAP_SIZE defines the minimum reserved heap size in linker layout
217+
* - the C runtime heap implementation uses the range [__heap_start, __heap_end)
218+
* - therefore actual heap available at runtime can be larger than __HEAP_SIZE
219+
* when there is extra free RAM between .bss/.heap and the stack region
212220
*/
213221
.heap (NOLOAD) : ALIGN(16)
214222
{
215223
. = ALIGN(16);
216224
PROVIDE( __heap_start = . );
225+
/* Reserve at least __HEAP_SIZE bytes for heap from low address upward. */
217226
. += __HEAP_SIZE;
218227
. = ALIGN(16);
219228
PROVIDE( __heap_limit = . );
@@ -222,11 +231,15 @@ SECTIONS
222231
.stack ORIGIN(RAM) + LENGTH(RAM) - __TOT_STACK_SIZE (NOLOAD) :
223232
{
224233
. = ALIGN(16);
234+
/* Heap can grow up to the start of the reserved stack area. */
225235
PROVIDE( _heap_end = . );
226236
PROVIDE( __heap_end = . );
227237
PROVIDE( __stack_start__ = . );
228238
PROVIDE( __StackLimit = . );
229239
PROVIDE( __StackBottom = . );
240+
/* Reserve per-hart stack slots at the top of RAM. In SMP, overflow may
241+
* corrupt a neighboring hart stack.
242+
*/
230243
. += __TOT_STACK_SIZE;
231244
. = ALIGN(16);
232245
PROVIDE( __StackTop = . );

doc/source/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ This is release version of ``0.9.0`` of Nuclei SDK.
219219
- Update documentation about n100 with eclic is now supported in Nuclei SDK
220220
- Add ``demo_smpcc`` and ``demo_ecc`` application documentation in ``app.rst``
221221
- Update :ref:`design_app_demo_profiling` documentation in ``app.rst`` with detailed GCC and LLVM/Clang toolchain-specific instructions for profiling and coverage analysis, and command-line workflows using ``lcov``
222+
- Clarify :ref:`develop_buildsystem_var_stacksz` and :ref:`develop_buildsystem_var_heapsz` documentation in ``buildsystem.rst`` to explain the default ``evalsoc`` stack/heap layout, per-hart SMP stack reservation, and that ``HEAPSZ`` only controls the minimum linker reservation while the effective runtime heap is bounded by ``__heap_start`` and ``__heap_end``
222223
- Add warning note in :ref:`develop_buildsystem_var_semihost` about heap and stack collision risk when using semihosting with ``malloc``, due to newlib semihost ``_sbrk`` implementation assumes unlimited heap size
223224

224225
* Test

doc/source/develop/buildsystem.rst

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1494,15 +1494,58 @@ ld options ``-Wl,--defsym=__STACK_SIZE=$(STACKSZ)`` to overwrite the default val
14941494
**STACKSZ** variable must be a valid value accepted by ld, such as 0x2000, 2K, 4K, 8192.
14951495

14961496
For SMP version, stack size space need to reserve **STACKSZ** x SMP Core Count size.
1497+
For the default ``evalsoc`` startup implementation, each hart gets its own stack window of
1498+
size ``__STACK_SIZE`` from the reserved stack area.
14971499

14981500
You can refer to ``SoC/evalsoc/Board/nuclei_fpga_eval/Source/GCC/gcc_evalsoc_ilm.ld`` for smp version.
14991501

1502+
For the default ``evalsoc`` GCC linker scripts in Nuclei SDK, the stack and heap are arranged
1503+
from opposite directions in the RAM region:
1504+
1505+
- ``.data/.bss`` and other runtime sections are placed first in RAM
1506+
- the ``.heap`` section starts after these runtime sections from low address to high address
1507+
- the linker script reserves at least ``__HEAP_SIZE`` bytes in ``.heap``
1508+
- the ``.stack`` section is placed at the top of RAM and grows downward logically, while the
1509+
linker reserves ``__STACK_SIZE * __SMP_CPU_CNT`` bytes for it
1510+
- ``__heap_end`` is defined at the start of the stack section, so the effective heap is bounded
1511+
by the free space between ``__heap_start`` and the reserved stack area
1512+
1513+
This means increasing ``STACKSZ`` reduces the remaining room available for heap growth, and
1514+
increasing ``HEAPSZ`` increases the minimum heap space reserved by the linker before the stack
1515+
area. In the default ``evalsoc`` runtime, however, ``malloc`` is bounded by ``__heap_start``
1516+
and ``__heap_end`` rather than by ``__HEAP_SIZE`` alone. So changing ``HEAPSZ`` by itself does
1517+
not necessarily increase the effective heap available at runtime unless the final linker layout
1518+
also leaves more room between the heap start and the reserved stack area. When debugging
1519+
memory-related issues, always consider stack size, heap reservation, actual section usage,
1520+
and total RAM size together.
1521+
1522+
For single-core layouts, ``STACKSZ`` can be understood as the minimum stack space reserved for
1523+
the current hart, and the runtime only needs to avoid colliding with the heap or other RAM
1524+
sections. For SMP layouts, this interpretation is no longer sufficient: each hart is assigned a
1525+
fixed stack slot of size ``__STACK_SIZE`` from the shared stack area. If one hart uses more than
1526+
its reserved stack slot, it may overwrite the neighboring hart stack instead of only consuming
1527+
more shared free RAM.
1528+
1529+
This layout keeps the linker script simple and makes memory reservation tuning easier through
1530+
``STACKSZ`` and ``HEAPSZ`` without requiring users to rewrite the linker script structure for
1531+
common adjustments. It also improves RAM utilization, because the heap can continue to use the
1532+
free space below the reserved stack area instead of being limited to a completely fixed-size
1533+
region. This design is especially useful for applications such as profiling and coverage, where
1534+
runtime memory demand can vary significantly between different builds and workloads.
1535+
1536+
.. note::
1537+
1538+
The linker script design in Nuclei SDK may vary by SoC, board, runtime library, or specific
1539+
memory map requirements. The ``evalsoc`` layout described here is a reference design to help
1540+
explain how ``STACKSZ`` and ``HEAPSZ`` interact, but it is not guaranteed to match every SoC
1541+
or every customer integration scenario exactly.
1542+
15001543
.. _develop_buildsystem_var_heapsz:
15011544

15021545
HEAPSZ
15031546
~~~~~~
15041547

1505-
**HEAPSZ** variable is used to control the heap size reserved in linker script,
1548+
**HEAPSZ** variable is used to control the minimum heap size reserved in linker script,
15061549
this need to cooperate with link script file and linker options.
15071550

15081551
In link script file, ``__HEAP_SIZE`` symbol need to use ``PROVIDE`` feature of ld
@@ -1512,6 +1555,20 @@ ld options ``-Wl,--defsym=__HEAP_SIZE=$(HEAPSZ)`` to overwrite the default value
15121555

15131556
**HEAPSZ** variable must be a valid value accepted by ld, such as 0x2000, 2K, 4K, 8192.
15141557

1558+
For the default C runtime heap implementations used by Nuclei SDK, such as newlib and
1559+
libncrt, the actual heap available to dynamic allocation is bounded by ``__heap_start``
1560+
and ``__heap_end``. In other words, ``__HEAP_SIZE`` is the minimum reserved heap size in
1561+
linker layout, while the effective runtime heap size depends on the final linker layout,
1562+
stack placement, and remaining RAM.
1563+
1564+
As a result, increasing ``HEAPSZ`` does not directly set the runtime heap size seen by
1565+
``malloc``. It only raises the minimum reservation that the linker must keep for the heap
1566+
region. If the final layout does not leave additional space between ``__heap_start`` and
1567+
``__heap_end``, the effective heap size may stay unchanged even after ``HEAPSZ`` is increased.
1568+
1569+
This is especially important for features such as profiling and coverage, which may allocate
1570+
large runtime buffers and therefore require a more relaxed memory layout.
1571+
15151572
.. _develop_buildsystem_var_riscv_arch:
15161573

15171574
RISCV_ARCH

0 commit comments

Comments
 (0)