[llvm] [Instrumentor] Runtime bitcode linking controls (PR #216333)
Vincent Arcila via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 14 07:38:51 PDT 2026
https://github.com/jandrovins created https://github.com/llvm/llvm-project/pull/216333
Add a `runtime_export_symbols` configuration option for Instrumentor runtime bitcode linking. Instrumentor already links runtime bitcode and can internalize linked runtime implementation details, but embedded users sometimes need selected runtime ABI symbols to remain externally visible after linking.
This adds string-list config support and uses it in `linkRuntime()` to preserve configured symbols during internalization. The default list is empty, so existing behavior is unchanged.
Tests verify that a listed runtime symbol remains external while an unlisted runtime symbol is still internalized, and update the default JSON config output.
>From 0fd94ee68387a62370a69e56c8bee44165c18662 Mon Sep 17 00:00:00 2001
From: "Vincent A. Arcila Larrea" <arcilalarrea1 at llnl.gov>
Date: Mon, 27 Jul 2026 16:39:39 -0700
Subject: [PATCH 1/3] [Instrumentor] Add .json option for keeping symbols
visible after linking
---
.../llvm/Transforms/IPO/Instrumentor.h | 28 +++++++++++++++
llvm/lib/Transforms/IPO/Instrumentor.cpp | 20 +++++++++--
.../Transforms/IPO/InstrumentorConfigFile.cpp | 35 +++++++++++++++++++
.../Instrumentor/default_config.json | 6 ++--
4 files changed, 85 insertions(+), 4 deletions(-)
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
index 5da53ac0ada15..6fbc620a7c4a9 100644
--- a/llvm/include/llvm/Transforms/IPO/Instrumentor.h
+++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
@@ -13,10 +13,12 @@
#ifndef LLVM_TRANSFORMS_IPO_INSTRUMENTOR_H
#define LLVM_TRANSFORMS_IPO_INSTRUMENTOR_H
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/EnumeratedArray.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
@@ -273,6 +275,7 @@ struct BaseConfigurationOption {
enum KindTy {
STRING,
BOOLEAN,
+ STRING_LIST,
};
/// Create a boolean option with \p Name name, \p Description description and
@@ -287,6 +290,13 @@ struct BaseConfigurationOption {
createStringOption(InstrumentationConfig &IC, StringRef Name,
StringRef Description, StringRef DefaultValue);
+ /// Create a string-list option with \p Name name, \p Description
+ /// description and \p DefaultValue as string-list default value.
+ LLVM_ABI static std::unique_ptr<BaseConfigurationOption>
+ createStringListOption(InstrumentationConfig &IC, StringRef Name,
+ StringRef Description,
+ ArrayRef<StringRef> DefaultValue);
+
/// Helper union that holds any possible option type.
union ValueTy {
bool Bool;
@@ -317,12 +327,26 @@ struct BaseConfigurationOption {
}
///}
+ /// Set and get of the string-list value. Only valid if it is a string-list
+ /// option.
+ ///{
+ void setStringList(ArrayRef<StringRef> Values) {
+ assert(Kind == STRING_LIST && "Not a string list!");
+ StringList.assign(Values.begin(), Values.end());
+ }
+ ArrayRef<StringRef> getStringList() const {
+ assert(Kind == STRING_LIST && "Not a string list!");
+ return StringList;
+ }
+ ///}
+
/// The information of the option.
///{
StringRef Name;
StringRef Description;
KindTy Kind;
ValueTy Value = {0};
+ SmallVector<StringRef> StringList;
///}
/// Construct a base configuration option.
@@ -374,6 +398,9 @@ struct LLVM_ABI InstrumentationConfig {
*this, "runtime_bitcode", "Link runtime bitcode", "");
InlineRuntimeEagerly = BaseConfigurationOption::createBoolOption(
*this, "inline_runtime", "Inline runtime function calls eagerly", true);
+ RuntimeExportSymbols = BaseConfigurationOption::createStringListOption(
+ *this, "runtime_export_symbols",
+ "Runtime symbols that remain externally visible after linking.", {});
populate(IIRB);
}
@@ -447,6 +474,7 @@ struct LLVM_ABI InstrumentationConfig {
std::unique_ptr<BaseConfigurationOption> GPUEnabled;
std::unique_ptr<BaseConfigurationOption> RuntimeBitcode;
std::unique_ptr<BaseConfigurationOption> InlineRuntimeEagerly;
+ std::unique_ptr<BaseConfigurationOption> RuntimeExportSymbols;
/// The map registered instrumentation opportunities. The map is indexed by
/// the instrumentation location kind and then by the opportunity name. Notice
diff --git a/llvm/lib/Transforms/IPO/Instrumentor.cpp b/llvm/lib/Transforms/IPO/Instrumentor.cpp
index bfa22199ef9f6..4bd1af2312af8 100644
--- a/llvm/lib/Transforms/IPO/Instrumentor.cpp
+++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp
@@ -262,8 +262,13 @@ void InstrumentorImpl::linkRuntime() {
}
auto InternalizeCallback = [&](Module &M, const StringSet<> &GVS) {
- internalizeModule(M, [&GVS](const GlobalValue &GV) {
- return !GV.hasName() || !GVS.count(GV.getName());
+ StringSet<> RuntimeExports;
+ for (StringRef Name : IConf.RuntimeExportSymbols->getStringList())
+ RuntimeExports.insert(Name);
+
+ internalizeModule(M, [&GVS, &RuntimeExports](const GlobalValue &GV) {
+ return !GV.hasName() || !GVS.count(GV.getName()) ||
+ RuntimeExports.count(GV.getName());
});
};
@@ -629,6 +634,17 @@ BaseConfigurationOption::createStringOption(InstrumentationConfig &IConf,
return BCO;
}
+std::unique_ptr<BaseConfigurationOption>
+BaseConfigurationOption::createStringListOption(
+ InstrumentationConfig &IConf, StringRef Name, StringRef Description,
+ ArrayRef<StringRef> DefaultValue) {
+ auto BCO = std::make_unique<BaseConfigurationOption>(Name, Description,
+ STRING_LIST);
+ BCO->setStringList(DefaultValue);
+ IConf.addBaseChoice(BCO.get());
+ return BCO;
+}
+
void InstrumentationConfig::populate(InstrumentorIRBuilderTy &IIRB) {
/// List of all instrumentation opportunities.
BasePointerIO::populate(*this, IIRB);
diff --git a/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp
index a2600b0551b65..083eca05b00f4 100644
--- a/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp
+++ b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp
@@ -69,6 +69,12 @@ void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile,
case BaseConfigurationOption::BOOLEAN:
J.attribute(BaseCO->Name, BaseCO->getBool());
break;
+ case BaseConfigurationOption::STRING_LIST:
+ J.attributeArray(BaseCO->Name, [&] {
+ for (StringRef Value : BaseCO->getStringList())
+ J.value(Value);
+ });
+ break;
}
if (!BaseCO->Description.empty())
J.attribute(std::string(BaseCO->Name) + ".description",
@@ -195,6 +201,35 @@ bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile,
DS_Warning));
}
break;
+ case BaseConfigurationOption::STRING_LIST: {
+ auto *Values = ObjIt.second.getAsArray();
+ if (!Values) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("configuration key '") + StringRef(ObjIt.first) +
+ Twine("' expects an array of strings, value ignored"),
+ DS_Warning));
+ break;
+ }
+
+ SmallVector<StringRef> Strings;
+ bool AllStrings = true;
+ for (const json::Value &Value : *Values) {
+ auto String = Value.getAsString();
+ if (!String) {
+ AllStrings = false;
+ break;
+ }
+ Strings.push_back(IConf.SS.save(*String));
+ }
+ if (AllStrings)
+ BO->setStringList(Strings);
+ else
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("configuration key '") + StringRef(ObjIt.first) +
+ Twine("' expects an array of strings, value ignored"),
+ DS_Warning));
+ break;
+ }
}
} else if (!StringRef(ObjIt.first).ends_with(".description")) {
std::string Diag = "configuration key '" + ObjIt.first.str() +
diff --git a/llvm/test/Instrumentation/Instrumentor/default_config.json b/llvm/test/Instrumentation/Instrumentor/default_config.json
index fc712537e5e22..06057833ed8a6 100644
--- a/llvm/test/Instrumentation/Instrumentor/default_config.json
+++ b/llvm/test/Instrumentation/Instrumentor/default_config.json
@@ -17,7 +17,9 @@
"runtime_bitcode": "",
"runtime_bitcode.description": "Link runtime bitcode",
"inline_runtime": true,
- "inline_runtime.description": "Inline runtime function calls eagerly"
+ "inline_runtime.description": "Inline runtime function calls eagerly",
+ "runtime_export_symbols": [],
+ "runtime_export_symbols.description": "Runtime symbols that remain externally visible after linking."
},
"module_pre": {
"module": {
@@ -446,4 +448,4 @@
"id.description": "A unique ID associated with the given instrumentor call"
}
}
-}
\ No newline at end of file
+}
>From 314f775d7f726d01da9b25721739afe26c6d60c1 Mon Sep 17 00:00:00 2001
From: "Vincent A. Arcila Larrea" <arcilalarrea1 at llnl.gov>
Date: Mon, 27 Jul 2026 16:48:28 -0700
Subject: [PATCH 2/3] [Instrumentor] Add runtime exports test
---
.../Instrumentor/runtime_export_symbols.ll | 14 ++++++++++++++
.../runtime_export_symbols_config.json | 15 +++++++++++++++
.../runtimes/runtime_export_symbols_rt.ll | 14 ++++++++++++++
3 files changed, 43 insertions(+)
create mode 100644 llvm/test/Instrumentation/Instrumentor/runtime_export_symbols.ll
create mode 100644 llvm/test/Instrumentation/Instrumentor/runtime_export_symbols_config.json
create mode 100644 llvm/test/Instrumentation/Instrumentor/runtimes/runtime_export_symbols_rt.ll
diff --git a/llvm/test/Instrumentation/Instrumentor/runtime_export_symbols.ll b/llvm/test/Instrumentation/Instrumentor/runtime_export_symbols.ll
new file mode 100644
index 0000000000000..5e822baa0a7a7
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/runtime_export_symbols.ll
@@ -0,0 +1,14 @@
+; RUN: llvm-as %S/runtimes/runtime_export_symbols_rt.ll -o runtime_export_symbols_rt.bc
+; RUN: opt < %s -passes=instrumentor -instrumentor-read-config-files=%S/runtime_export_symbols_config.json -S | FileCheck %s
+
+; CHECK: @runtime_export = linkonce_odr global i32 0, comdat, align 4
+; CHECK: @runtime_internal = internal global i32 0, align 4
+
+ at runtime_export = external global i32
+ at runtime_internal = external global i32
+
+define i32 @test(i32 %lhs, i32 %rhs) {
+entry:
+ %result = add i32 %lhs, %rhs
+ ret i32 %result
+}
\ No newline at end of file
diff --git a/llvm/test/Instrumentation/Instrumentor/runtime_export_symbols_config.json b/llvm/test/Instrumentation/Instrumentor/runtime_export_symbols_config.json
new file mode 100644
index 0000000000000..2519dfb3796b3
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/runtime_export_symbols_config.json
@@ -0,0 +1,15 @@
+{
+ "configuration": {
+ "runtime_prefix": "__runtime_export_",
+ "runtime_bitcode": "runtime_export_symbols_rt.bc",
+ "inline_runtime": false,
+ "runtime_export_symbols": ["runtime_export"]
+ },
+ "instruction_pre": {
+ "numeric": {
+ "enabled": true,
+ "size": true,
+ "id": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/llvm/test/Instrumentation/Instrumentor/runtimes/runtime_export_symbols_rt.ll b/llvm/test/Instrumentation/Instrumentor/runtimes/runtime_export_symbols_rt.ll
new file mode 100644
index 0000000000000..342dd718271b9
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/runtimes/runtime_export_symbols_rt.ll
@@ -0,0 +1,14 @@
+$runtime_export = comdat any
+$runtime_internal = comdat any
+
+ at runtime_export = linkonce_odr global i32 0, comdat, align 4
+ at runtime_internal = linkonce_odr global i32 0, comdat, align 4
+
+define void @__runtime_export_pre_numeric(i32 %size, i32 %id) {
+entry:
+ %export = load i32, ptr @runtime_export, align 4
+ %internal = load i32, ptr @runtime_internal, align 4
+ %sum = add i32 %export, %internal
+ store i32 %sum, ptr @runtime_internal, align 4
+ ret void
+}
\ No newline at end of file
>From 7d319f01dbd7dab2c3ba7420e55dea99c008f534 Mon Sep 17 00:00:00 2001
From: "Vincent A. Arcila Larrea" <arcilalarrea1 at llnl.gov>
Date: Fri, 14 Aug 2026 07:34:06 -0700
Subject: [PATCH 3/3] [Instrumentor] Format
---
llvm/include/llvm/Transforms/IPO/Instrumentor.h | 4 ++--
llvm/lib/Transforms/IPO/Instrumentor.cpp | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
index 6fbc620a7c4a9..915c3f3e35476 100644
--- a/llvm/include/llvm/Transforms/IPO/Instrumentor.h
+++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
@@ -399,8 +399,8 @@ struct LLVM_ABI InstrumentationConfig {
InlineRuntimeEagerly = BaseConfigurationOption::createBoolOption(
*this, "inline_runtime", "Inline runtime function calls eagerly", true);
RuntimeExportSymbols = BaseConfigurationOption::createStringListOption(
- *this, "runtime_export_symbols",
- "Runtime symbols that remain externally visible after linking.", {});
+ *this, "runtime_export_symbols",
+ "Runtime symbols that remain externally visible after linking.", {});
populate(IIRB);
}
diff --git a/llvm/lib/Transforms/IPO/Instrumentor.cpp b/llvm/lib/Transforms/IPO/Instrumentor.cpp
index 4bd1af2312af8..fc5b8a9845bef 100644
--- a/llvm/lib/Transforms/IPO/Instrumentor.cpp
+++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp
@@ -638,8 +638,8 @@ std::unique_ptr<BaseConfigurationOption>
BaseConfigurationOption::createStringListOption(
InstrumentationConfig &IConf, StringRef Name, StringRef Description,
ArrayRef<StringRef> DefaultValue) {
- auto BCO = std::make_unique<BaseConfigurationOption>(Name, Description,
- STRING_LIST);
+ auto BCO =
+ std::make_unique<BaseConfigurationOption>(Name, Description, STRING_LIST);
BCO->setStringList(DefaultValue);
IConf.addBaseChoice(BCO.get());
return BCO;
More information about the llvm-commits
mailing list