Skip to content

Commit b20aec8

Browse files
tests: llext: Test RISC-V non-paired relocation
Tests an edge case in the RISC-V PSABI: In the medany and the medlow code models, the compiler emits auipc/lui (U-type) and ld/sw (I-type/S-type) instruction pairs for accessing a non-local symbol. The U-type instruction sets the upper 20 bits, the I/S-type the lower 12. The U-type and I-type/S-type instruction pairs are often adjacent in code. This is also what the current llext architecture-specific relocations expect. However, this need not be the case - compilers can re-use the upper 20 bits set by the U-type instruction with multiple I/S-type instructions, which is a useful optimization for multiple loads/stores of or within the same symbol. This commit adds a unit test for this behavior, which currently fails for RISC-V. Signed-off-by: Eric Ackermann <[email protected]>
1 parent f4c989f commit b20aec8

File tree

4 files changed

+113
-0
lines changed

4 files changed

+113
-0
lines changed

tests/subsys/llext/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,13 @@ if(NOT CONFIG_LLEXT_TYPE_ELF_OBJECT AND CONFIG_RISCV AND CONFIG_RISCV_ISA_EXT_C)
9191
${ZEPHYR_BINARY_DIR}/include/generated/riscv_edge_case_cb_type.inc
9292
)
9393
endif()
94+
95+
if(NOT CONFIG_LLEXT_TYPE_ELF_OBJECT AND CONFIG_RISCV)
96+
add_llext_target(riscv_edge_case_non_paired_hi20_lo12_ext
97+
OUTPUT ${PROJECT_BINARY_DIR}/llext/riscv_edge_case_non_paired_hi20_lo12_ext.llext
98+
SOURCES ${PROJECT_SOURCE_DIR}/src/riscv_edge_case_non_paired_hi20_lo12.c ${PROJECT_SOURCE_DIR}/src/riscv_edge_case_non_paired_hi20_lo12_trigger.S
99+
)
100+
generate_inc_file_for_target(app ${PROJECT_BINARY_DIR}/llext/riscv_edge_case_non_paired_hi20_lo12_ext.llext
101+
${ZEPHYR_BINARY_DIR}/include/generated/riscv_edge_case_non_paired_hi20_lo12.inc
102+
)
103+
endif()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2024 CISPA Helmholtz Center for Information Security
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/*
8+
* This extension tests a relocation edge case in RISC-V:
9+
* U-type instructions in conjunction with I-type/S-type instructions can be used to
10+
* relocate symbols within a 32-bit range from the PC (medany code model) or 0
11+
* (medlow code model).
12+
* The compiler usually emits the U-type instructions and I-type/S-type instructions in sequence.
13+
* However, this is not guaranteed.
14+
* The accompanying assembly listing generates a scenario in which this assumption does NOT hold
15+
* and tests that the llext loader can handle it.
16+
*/
17+
18+
#include <stdbool.h>
19+
#include <zephyr/llext/symbol.h>
20+
#include <zephyr/ztest_assert.h>
21+
22+
extern int _riscv_edge_case_non_paired_hi20_lo12(void);
23+
24+
25+
#define DATA_SEGMENT_SYMBOL_INITIAL 21
26+
#define DATA_SEGMENT_SYMBOL_EXPECTED (DATA_SEGMENT_SYMBOL_INITIAL+42)
27+
28+
/* changed from the assembly script */
29+
volatile int _data_segment_symbol = DATA_SEGMENT_SYMBOL_INITIAL;
30+
31+
32+
void test_entry(void)
33+
{
34+
int ret_value;
35+
36+
ret_value = _riscv_edge_case_non_paired_hi20_lo12();
37+
38+
zassert_equal(ret_value, DATA_SEGMENT_SYMBOL_INITIAL);
39+
40+
zassert_equal(_data_segment_symbol, DATA_SEGMENT_SYMBOL_EXPECTED);
41+
}
42+
EXPORT_SYMBOL(test_entry);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2025 CISPA Helmholtz Center for Information Security
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/toolchain.h>
8+
9+
/* 32-bit global defined in C */
10+
GDATA(_data_segment_symbol)
11+
12+
GTEXT(_riscv_edge_case_non_paired_hi20_lo12)
13+
14+
/*
15+
* Tests an edge case in the RISC-V PSABI: In the medany and the medlow code models,
16+
* the compiler emits auipc/lui (U-type) and ld/sw (I-type/S-type) instruction pairs
17+
* for accessing a non-local symbol.
18+
* The U-type instruction sets the upper 20 bits, the I/S-type the lower 12.
19+
* Thus, any address in a 32-bit range from 0 (medlow) / the PC (medany) can be reached.
20+
* Often, the U-type and I-type/S-type instruction pairs are adjacent in code.
21+
* However, this need not be the case - compilers can re-use the upper 20 bits set by
22+
* the U-type instruction with multiple I/S-type instructions, which is a useful optimization
23+
* for multiple loads/stores of or within the same symbol.
24+
* The U-type instruction can also appear after the I-type in code, e.g., due to control flow.
25+
* When the U-type and I/S-type instructions are not in sequence, this triggers an edge case
26+
* in the llext loader.
27+
* This test triggers this edge case by loading a global, modifying it and storing it back.
28+
*/
29+
SECTION_FUNC(TEXT, _riscv_edge_case_non_paired_hi20_lo12)
30+
/* jump beyond the I-type/load instruction initially to break sequence assumption */
31+
j _do_utype
32+
33+
_do_load:
34+
/* re-use the upper-bit value set by the U-type below for a load */
35+
lw a0, %pcrel_lo(.LUtype)(a1)
36+
37+
addi t1, a0, 42
38+
39+
j _do_store
40+
41+
_do_utype:
42+
/* this u-type sets the higher 20 bits of the global */
43+
.LUtype: auipc a1, %pcrel_hi(_data_segment_symbol)
44+
45+
/* backwards jump to test loading */
46+
j _do_load
47+
48+
_do_store:
49+
50+
/* write the modified value back for the C code to check */
51+
sw t1, %pcrel_lo(.LUtype)(a1)
52+
53+
/* return a0, i.e., the value we read, for the C code to check */
54+
ret

tests/subsys/llext/src/test_llext.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,13 @@ static LLEXT_CONST uint8_t riscv_edge_case_cb_type_ext[] ELF_ALIGN = {
396396
LLEXT_LOAD_UNLOAD(riscv_edge_case_cb_type)
397397
#endif /* CONFIG_RISCV && CONFIG_RISCV_ISA_EXT_C */
398398

399+
#if defined(CONFIG_RISCV)
400+
static LLEXT_CONST uint8_t riscv_edge_case_non_paired_hi20_lo12_ext[] ELF_ALIGN = {
401+
#include "riscv_edge_case_non_paired_hi20_lo12.inc"
402+
};
403+
LLEXT_LOAD_UNLOAD(riscv_edge_case_non_paired_hi20_lo12)
404+
#endif /* CONFIG_RISCV */
405+
399406
#endif /* !CONFIG_LLEXT_TYPE_ELF_OBJECT */
400407

401408
#ifndef CONFIG_USERSPACE

0 commit comments

Comments
 (0)