[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