[llvm] [orc-rt] Add initial C addressing regression tests. (PR #226901)
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 28 00:12:45 PDT 2026
https://github.com/lhames created https://github.com/llvm/llvm-project/pull/226901
Add tests that check that JIT'd C code can address data, both in the same object and in other objects. Each test covers a single construct (e.g. a load of static data, or a pointer to an array element in another object stored in initialized data), since linker/loader bugs usually crash the JIT'd program, and a crash identifies only the failing test. Each test runs at -O0 and -O2.
To support multi-object tests, add a split-file substitution and lit feature, and accept .test files in languages/c.
Update the README's test conventions to match: one construct per test, "Check that" and "Stresses:" header comments, -O0 and -O2 RUN lines, and guidance on keeping constructs alive under optimization without hiding the optimized lowering.
Assisted-by: Claude
>From cb4e41e65dfcbcb9a96832f50a935c70bb7cb9ae Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Mon, 28 Sep 2026 15:39:14 +1000
Subject: [PATCH] [orc-rt] Add initial C addressing regression tests.
Add tests that check that JIT'd C code can address data, both in the same
object and in other objects. Each test covers a single construct (e.g. a load
of static data, or a pointer to an array element in another object stored in
initialized data), since linker/loader bugs usually crash the JIT'd program,
and a crash identifies only the failing test. Each test runs at -O0 and -O2.
To support multi-object tests, add a split-file substitution and lit feature,
and accept .test files in languages/c.
Update the README's test conventions to match: one construct per test,
"Check that" and "Stresses:" header comments, -O0 and -O2 RUN lines, and
guidance on keeping constructs alive under optimization without hiding the
optimized lowering.
Assisted-by: Claude
---
orc-rt/cmake/OrcRTTesting.cmake | 2 +-
orc-rt/test/regression/README.md | 116 ++++++++++++++++--
.../global-array-element-pointer-in-data.test | 29 +++++
.../cross-object/global-data-load.test | 27 ++++
.../cross-object/hidden-data-load.test | 28 +++++
.../same-object/global-data-pointer-in-data.c | 22 ++++
.../same-object/global-data-store.c | 21 ++++
.../same-object/static-array-element-load.c | 21 ++++
.../addressing/same-object/static-data-load.c | 19 +++
.../test/regression/languages/c/lit.local.cfg | 5 +-
orc-rt/test/regression/lit.cfg.py | 8 ++
11 files changed, 289 insertions(+), 9 deletions(-)
create mode 100644 orc-rt/test/regression/languages/c/addressing/cross-object/global-array-element-pointer-in-data.test
create mode 100644 orc-rt/test/regression/languages/c/addressing/cross-object/global-data-load.test
create mode 100644 orc-rt/test/regression/languages/c/addressing/cross-object/hidden-data-load.test
create mode 100644 orc-rt/test/regression/languages/c/addressing/same-object/global-data-pointer-in-data.c
create mode 100644 orc-rt/test/regression/languages/c/addressing/same-object/global-data-store.c
create mode 100644 orc-rt/test/regression/languages/c/addressing/same-object/static-array-element-load.c
create mode 100644 orc-rt/test/regression/languages/c/addressing/same-object/static-data-load.c
diff --git a/orc-rt/cmake/OrcRTTesting.cmake b/orc-rt/cmake/OrcRTTesting.cmake
index bd1812a3f72fe..bb15652ddea01 100644
--- a/orc-rt/cmake/OrcRTTesting.cmake
+++ b/orc-rt/cmake/OrcRTTesting.cmake
@@ -37,7 +37,7 @@ endif()
# Add dependencies on optional tools, if they're being built alongside us.
# Tests that need these tools are gated on lit features, so they're not required.
-foreach(tool clang llvm-jitlink llvm-mc)
+foreach(tool clang llvm-jitlink llvm-mc split-file)
if (TARGET ${tool})
list(APPEND ORC_RT_TEST_DEPS ${tool})
endif()
diff --git a/orc-rt/test/regression/README.md b/orc-rt/test/regression/README.md
index 71ede5606692f..2d424308eb1aa 100644
--- a/orc-rt/test/regression/README.md
+++ b/orc-rt/test/regression/README.md
@@ -24,21 +24,44 @@ and `%{obj-jit}` substitutions are unavailable there.
A typical test:
```c
-// Check that a trivial C program can be compiled and run under ogre.
+// Check that JIT'd code can load from static data defined in the same object.
//
-// RUN: %{cc} -c -o %t.o %s
-// RUN: %{obj-jit} -show-jit-result %t.o | FileCheck %s
+// Stresses: fixups for code addressing data in the same object.
+//
+// RUN: %{cc} -O0 -c -o %t.O0.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O0.o | FileCheck %s
+// RUN: %{cc} -O2 -c -o %t.O2.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O2.o | FileCheck %s
// CHECK: JIT result: 0
-int main(void) { return 0; }
+static int Data = 42;
+
+int main(void) {
+ // Volatile load, to prevent constant propagation of the initializer.
+ if (*(volatile int *)&Data != 42)
+ return 1;
+ return 0;
+}
```
Conventions:
-* **Return 0 on success, and a distinct non-zero value from each check.**
- A failure then reports which check failed (`JIT result: 3`), not just that
- the test failed.
+* **Test one construct per test.** Linker and loader bugs usually crash the
+ JIT'd program rather than return a wrong value, and a crash identifies only
+ the test, not the check that crashed. Keep each test to a single source
+ construct (in language tests) or object format feature (in object format
+ tests), so that the name of a failing test says what broke. Tests of how
+ constructs interact are the exception, and should say so.
+* **Say what the test checks, and what it stresses.** Start with a one-line
+ "Check that ..." summary of the behavior under test, followed by a
+ "Stresses:" line naming the linker or loader behavior that it depends on.
+ This tells someone triaging a failure where to look. Be specific: name the
+ fixups, sections, or optimizations involved.
+* **Return 0 on success.** If a test makes more than one check of its
+ construct (e.g. that data reads correctly, then that it can be written),
+ return a distinct non-zero value from each, so that a failure reports which
+ check failed (`JIT result: 2`).
* **Check behavior, not object contents.** Language tests should keep passing
if the compiler changes how a construct is lowered, and only fail if the
new lowering doesn't work under the runtime.
@@ -46,6 +69,84 @@ Conventions:
library functions you need yourself. This keeps tests usable when
cross-compiling for targets without a sysroot.
+For tests compiled from source:
+
+* **Test at `-O0` and `-O2`,** with RUN lines for each. Most JIT'd code is
+ optimized, and optimization changes how constructs are lowered. If a
+ construct can only be tested at one level, say which, and why.
+* **Keep constructs alive under optimization, but no more.** An optimizing
+ compiler may fold away a construct (e.g. replace a load of static data with
+ its initializer), leaving nothing for the JIT linker to handle. Prefer
+ making the construct opaque to the compiler (e.g. defining data in another
+ object, or with external linkage) over constraining it with `volatile` or
+ `noinline`, which may hide the optimized lowering we want to test. When a
+ test needs a barrier, apply the weakest one to the construct under test
+ only, and name the optimization it suppresses (e.g. `// Volatile load, to
+ prevent store-to-load forwarding.`).
+* **Don't assume a particular compiler.** `%{cc}` and `%{cxx}` may be
+ user-supplied (see below). Say what a compiler *may* do, not what it will
+ do, and use generic optimization names rather than those of a particular
+ compiler's passes.
+
+### Tests with more than one source file
+
+Tests that need more than one source file (e.g. to link several objects) are
+`.test` files that hold their sources, split out with `split-file`:
+
+```
+# Check that JIT'd code can load from global data defined in another object.
+#
+# Stresses: fixups for code addressing data defined in another object, which
+# the compiler usually addresses through the GOT.
+
+REQUIRES: split-file
+RUN: rm -rf %t && split-file %s %t
+RUN: %{cc} -O0 -c -o %t/def.O0.o %t/def.c
+RUN: %{cc} -O0 -c -o %t/main.O0.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O0.o %t/def.O0.o | FileCheck %s
+RUN: %{cc} -O2 -c -o %t/def.O2.o %t/def.c
+RUN: %{cc} -O2 -c -o %t/main.O2.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O2.o %t/def.O2.o | FileCheck %s
+
+CHECK: JIT result: 0
+
+#--- def.c
+int Data = 42;
+
+#--- main.c
+extern int Data;
+
+int main(void) {
+ if (Data != 42)
+ return 1;
+ return 0;
+}
+```
+
+### Tests with more than one source file
+
+Tests that need more than one source file (e.g. to link several objects) are
+`.test` files that hold their sources, split out with `split-file`:
+
+```
+# Check that JIT'd code can load from global data defined in another object.
+
+REQUIRES: split-file
+RUN: rm -rf %t && split-file %s %t
+RUN: %{cc} -O0 -c -o %t/def.O0.o %t/def.c
+RUN: %{cc} -O0 -c -o %t/main.O0.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O0.o %t/def.O0.o | FileCheck %s
+
+CHECK: JIT result: 0
+
+#--- def.c
+int Data = 42;
+
+#--- main.c
+extern int Data;
+int main(void) { return Data == 42 ? 0 : 1; }
+```
+
### Substitutions
* **`%{cc}`**: Compiles C for the runtime's target.
@@ -79,6 +180,7 @@ the host:
* **`orc-rt-cxx`**: `%{cxx}` is usable.
* **`llvm-mc`**: `%{mc}` is usable.
* **`llvm-jitlink`**: `%{obj-jit}` is usable.
+* **`split-file`**: `split-file` is usable.
* **`target-arch=<arch>`**: The runtime's target architecture (`arm64` and
`aarch64` are aliases).
* **`target-object-format=<coff|elf|mach-o>`**: The runtime's target object
diff --git a/orc-rt/test/regression/languages/c/addressing/cross-object/global-array-element-pointer-in-data.test b/orc-rt/test/regression/languages/c/addressing/cross-object/global-array-element-pointer-in-data.test
new file mode 100644
index 0000000000000..87834d2dc711d
--- /dev/null
+++ b/orc-rt/test/regression/languages/c/addressing/cross-object/global-array-element-pointer-in-data.test
@@ -0,0 +1,29 @@
+# Check that a pointer to an element of a global array defined in another
+# object, stored in initialized data, points to that element.
+#
+# Stresses: fixups with addends for pointers stored in data, targeting data
+# defined in another object.
+
+REQUIRES: split-file
+RUN: rm -rf %t && split-file %s %t
+RUN: %{cc} -O0 -c -o %t/def.O0.o %t/def.c
+RUN: %{cc} -O0 -c -o %t/main.O0.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O0.o %t/def.O0.o | FileCheck %s
+RUN: %{cc} -O2 -c -o %t/def.O2.o %t/def.c
+RUN: %{cc} -O2 -c -o %t/main.O2.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O2.o %t/def.O2.o | FileCheck %s
+
+CHECK: JIT result: 0
+
+#--- def.c
+int Array[4096] = {[1500] = 42};
+
+#--- main.c
+extern int Array[4096];
+int *ElementPtr = &Array[1500];
+
+int main(void) {
+ if (*ElementPtr != 42)
+ return 1;
+ return 0;
+}
diff --git a/orc-rt/test/regression/languages/c/addressing/cross-object/global-data-load.test b/orc-rt/test/regression/languages/c/addressing/cross-object/global-data-load.test
new file mode 100644
index 0000000000000..9659b0d2e823d
--- /dev/null
+++ b/orc-rt/test/regression/languages/c/addressing/cross-object/global-data-load.test
@@ -0,0 +1,27 @@
+# Check that JIT'd code can load from global data defined in another object.
+#
+# Stresses: fixups for code addressing data defined in another object, which
+# the compiler usually addresses through the GOT.
+
+REQUIRES: split-file
+RUN: rm -rf %t && split-file %s %t
+RUN: %{cc} -O0 -c -o %t/def.O0.o %t/def.c
+RUN: %{cc} -O0 -c -o %t/main.O0.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O0.o %t/def.O0.o | FileCheck %s
+RUN: %{cc} -O2 -c -o %t/def.O2.o %t/def.c
+RUN: %{cc} -O2 -c -o %t/main.O2.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O2.o %t/def.O2.o | FileCheck %s
+
+CHECK: JIT result: 0
+
+#--- def.c
+int Data = 42;
+
+#--- main.c
+extern int Data;
+
+int main(void) {
+ if (Data != 42)
+ return 1;
+ return 0;
+}
diff --git a/orc-rt/test/regression/languages/c/addressing/cross-object/hidden-data-load.test b/orc-rt/test/regression/languages/c/addressing/cross-object/hidden-data-load.test
new file mode 100644
index 0000000000000..d56175cbccde2
--- /dev/null
+++ b/orc-rt/test/regression/languages/c/addressing/cross-object/hidden-data-load.test
@@ -0,0 +1,28 @@
+# Check that JIT'd code can load from hidden data defined in another object.
+#
+# Stresses: fixups for code addressing data defined in another object
+# directly, rather than through the GOT. Hidden data must be defined in the
+# same linkage unit, so the compiler may address it directly.
+
+REQUIRES: split-file
+RUN: rm -rf %t && split-file %s %t
+RUN: %{cc} -O0 -c -o %t/def.O0.o %t/def.c
+RUN: %{cc} -O0 -c -o %t/main.O0.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O0.o %t/def.O0.o | FileCheck %s
+RUN: %{cc} -O2 -c -o %t/def.O2.o %t/def.c
+RUN: %{cc} -O2 -c -o %t/main.O2.o %t/main.c
+RUN: %{obj-jit} -show-jit-result %t/main.O2.o %t/def.O2.o | FileCheck %s
+
+CHECK: JIT result: 0
+
+#--- def.c
+__attribute__((visibility("hidden"))) int Data = 42;
+
+#--- main.c
+extern __attribute__((visibility("hidden"))) int Data;
+
+int main(void) {
+ if (Data != 42)
+ return 1;
+ return 0;
+}
diff --git a/orc-rt/test/regression/languages/c/addressing/same-object/global-data-pointer-in-data.c b/orc-rt/test/regression/languages/c/addressing/same-object/global-data-pointer-in-data.c
new file mode 100644
index 0000000000000..8ee0e86e5be18
--- /dev/null
+++ b/orc-rt/test/regression/languages/c/addressing/same-object/global-data-pointer-in-data.c
@@ -0,0 +1,22 @@
+// Check that a pointer to global data, stored in initialized data in the same
+// object, points to that data.
+//
+// Stresses: fixups for pointers stored in data, targeting data in the same
+// object.
+//
+// RUN: %{cc} -O0 -c -o %t.O0.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O0.o | FileCheck %s
+// RUN: %{cc} -O2 -c -o %t.O2.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O2.o | FileCheck %s
+
+// CHECK: JIT result: 0
+
+// No barrier needed: non-const globals' values can't be propagated.
+int Data = 42;
+int *DataPtr = &Data;
+
+int main(void) {
+ if (*DataPtr != 42)
+ return 1;
+ return 0;
+}
diff --git a/orc-rt/test/regression/languages/c/addressing/same-object/global-data-store.c b/orc-rt/test/regression/languages/c/addressing/same-object/global-data-store.c
new file mode 100644
index 0000000000000..1329a4eb25224
--- /dev/null
+++ b/orc-rt/test/regression/languages/c/addressing/same-object/global-data-store.c
@@ -0,0 +1,21 @@
+// Check that JIT'd code can store to global data defined in the same object.
+//
+// Stresses: fixups for code addressing data in the same object, and that the
+// data is writable.
+//
+// RUN: %{cc} -O0 -c -o %t.O0.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O0.o | FileCheck %s
+// RUN: %{cc} -O2 -c -o %t.O2.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O2.o | FileCheck %s
+
+// CHECK: JIT result: 0
+
+int Data = 1;
+
+int main(void) {
+ Data = 42;
+ // Volatile load, to prevent store-to-load forwarding.
+ if (*(volatile int *)&Data != 42)
+ return 1;
+ return 0;
+}
diff --git a/orc-rt/test/regression/languages/c/addressing/same-object/static-array-element-load.c b/orc-rt/test/regression/languages/c/addressing/same-object/static-array-element-load.c
new file mode 100644
index 0000000000000..f966ebe677af1
--- /dev/null
+++ b/orc-rt/test/regression/languages/c/addressing/same-object/static-array-element-load.c
@@ -0,0 +1,21 @@
+// Check that JIT'd code can load an element of a static array defined in the
+// same object, at an offset of more than a page from the start of the array.
+//
+// Stresses: fixups with addends for code addressing data in the same object,
+// where the addend moves the target onto a different page from the symbol.
+//
+// RUN: %{cc} -O0 -c -o %t.O0.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O0.o | FileCheck %s
+// RUN: %{cc} -O2 -c -o %t.O2.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O2.o | FileCheck %s
+
+// CHECK: JIT result: 0
+
+static int Array[4096] = {[1500] = 42};
+
+int main(void) {
+ // Volatile load, to prevent constant propagation of the initializer.
+ if (*(volatile int *)&Array[1500] != 42)
+ return 1;
+ return 0;
+}
diff --git a/orc-rt/test/regression/languages/c/addressing/same-object/static-data-load.c b/orc-rt/test/regression/languages/c/addressing/same-object/static-data-load.c
new file mode 100644
index 0000000000000..6701f9a2b59d6
--- /dev/null
+++ b/orc-rt/test/regression/languages/c/addressing/same-object/static-data-load.c
@@ -0,0 +1,19 @@
+// Check that JIT'd code can load from static data defined in the same object.
+//
+// Stresses: fixups for code addressing data in the same object.
+//
+// RUN: %{cc} -O0 -c -o %t.O0.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O0.o | FileCheck %s
+// RUN: %{cc} -O2 -c -o %t.O2.o %s
+// RUN: %{obj-jit} -show-jit-result %t.O2.o | FileCheck %s
+
+// CHECK: JIT result: 0
+
+static int Data = 42;
+
+int main(void) {
+ // Volatile load, to prevent constant propagation of the initializer.
+ if (*(volatile int *)&Data != 42)
+ return 1;
+ return 0;
+}
diff --git a/orc-rt/test/regression/languages/c/lit.local.cfg b/orc-rt/test/regression/languages/c/lit.local.cfg
index fb7ddd3f1a0f1..71b381a7fdf62 100644
--- a/orc-rt/test/regression/languages/c/lit.local.cfg
+++ b/orc-rt/test/regression/languages/c/lit.local.cfg
@@ -1,7 +1,10 @@
# Tests that C source constructs behave correctly when JIT'd under the ORC
# runtime. Tests compile with %{cc} and run the result with %{obj-jit}.
+#
+# Single-object tests are .c files. Tests that link several objects are .test
+# files, which use split-file to hold their sources.
-config.suffixes = [".c"]
+config.suffixes = [".c", ".test"]
if not {"orc-rt-cc", "llvm-jitlink"} <= config.available_features:
config.unsupported = True
diff --git a/orc-rt/test/regression/lit.cfg.py b/orc-rt/test/regression/lit.cfg.py
index 413a4e8bd505a..86c25461d13db 100644
--- a/orc-rt/test/regression/lit.cfg.py
+++ b/orc-rt/test/regression/lit.cfg.py
@@ -82,6 +82,14 @@ def add_compiler(name, clang_name):
)
)
+# split-file splits a test into several files, e.g. so that a test can build
+# and link more than one object. Tests that use it must be gated on the
+# split-file feature.
+split_file = llvm_config.use_llvm_tool("split-file")
+if split_file:
+ config.available_features.add("split-file")
+ llvm_config.add_tool_substitutions([ToolSubst("split-file", command=split_file)])
+
# Describe the runtime's target architecture and object format, so that object
# format tests can gate on them:
# target-arch=<arch> (arm64 and aarch64 are aliases)
More information about the llvm-commits
mailing list