[llvm] [Instrumentor] Runtime bitcode linking controls (PR #216333)
Vincent Arcila via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 14 08:15:34 PDT 2026
https://github.com/jandrovins updated https://github.com/llvm/llvm-project/pull/216333
>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/4] [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/4] [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/4] [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;
>From 0dfbacd0bc6ce9c244e24d47497507eeb0122b72 Mon Sep 17 00:00:00 2001
From: "Vincent A. Arcila Larrea" <arcilalarrea1 at llnl.gov>
Date: Fri, 14 Aug 2026 07:59:58 -0700
Subject: [PATCH 4/4] [Instrumentor] Add pre-runtime-link extension point
---
.../llvm/Transforms/IPO/Instrumentor.h | 7 ++
llvm/lib/Transforms/IPO/Instrumentor.cpp | 2 +
llvm/unittests/Transforms/IPO/CMakeLists.txt | 1 +
.../Transforms/IPO/InstrumentorTest.cpp | 94 +++++++++++++++++++
4 files changed, 104 insertions(+)
create mode 100644 llvm/unittests/Transforms/IPO/InstrumentorTest.cpp
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
index 915c3f3e35476..34a4cea86d220 100644
--- a/llvm/include/llvm/Transforms/IPO/Instrumentor.h
+++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
@@ -407,6 +407,13 @@ struct LLVM_ABI InstrumentationConfig {
/// Populate the instrumentation opportunities.
virtual void populate(InstrumentorIRBuilderTy &IIRB);
+ /// Allow embedded users to extend the module after instrumentation and before
+ /// runtime bitcode linking.
+ virtual bool instrumentBeforeRuntimeLink(Module &,
+ InstrumentorIRBuilderTy &) {
+ return false;
+ }
+
/// Get the runtime prefix for the instrumentation runtime functions.
StringRef getRTName() const { return RuntimePrefix->getString(); }
diff --git a/llvm/lib/Transforms/IPO/Instrumentor.cpp b/llvm/lib/Transforms/IPO/Instrumentor.cpp
index fc5b8a9845bef..44e95c32e1d43 100644
--- a/llvm/lib/Transforms/IPO/Instrumentor.cpp
+++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp
@@ -541,6 +541,8 @@ bool InstrumentorImpl::instrument() {
for (Function &Fn : M)
Changed |= instrumentFunction(Fn);
+ Changed |= IConf.instrumentBeforeRuntimeLink(M, IIRB);
+
linkRuntime();
return Changed;
diff --git a/llvm/unittests/Transforms/IPO/CMakeLists.txt b/llvm/unittests/Transforms/IPO/CMakeLists.txt
index 5b45191afc711..a54bf05b047b5 100644
--- a/llvm/unittests/Transforms/IPO/CMakeLists.txt
+++ b/llvm/unittests/Transforms/IPO/CMakeLists.txt
@@ -14,5 +14,6 @@ add_llvm_unittest(IPOTests
AttributorTest.cpp
FunctionSpecializationTest.cpp
ImportIDTableTests.cpp
+ InstrumentorTest.cpp
MergeFunctionsTest.cpp
)
diff --git a/llvm/unittests/Transforms/IPO/InstrumentorTest.cpp b/llvm/unittests/Transforms/IPO/InstrumentorTest.cpp
new file mode 100644
index 0000000000000..4f89ada4dbb35
--- /dev/null
+++ b/llvm/unittests/Transforms/IPO/InstrumentorTest.cpp
@@ -0,0 +1,94 @@
+//===- InstrumentorTest.cpp - Unit tests for InstrumentorPass -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/IPO/Instrumentor.h"
+
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::instrumentor;
+
+namespace {
+
+class PreRuntimeLinkConfig final : public InstrumentationConfig {
+public:
+ explicit PreRuntimeLinkConfig(StringRef RuntimePath)
+ : RuntimePath(RuntimePath) {}
+
+ void populate(InstrumentorIRBuilderTy &) override {
+ RuntimeBitcode->setString(RuntimePath);
+ }
+
+ bool instrumentBeforeRuntimeLink(Module &M,
+ InstrumentorIRBuilderTy &) override {
+ LLVMContext &Ctx = M.getContext();
+ Type *Ty = Type::getInt1Ty(Ctx);
+ Constant *SawRuntime =
+ ConstantInt::get(Ty, M.getNamedGlobal("runtime_marker") != nullptr);
+ new GlobalVariable(M, Ty, false, GlobalValue::ExternalLinkage, SawRuntime,
+ "hook_saw_runtime");
+ return true;
+ }
+
+private:
+ std::string RuntimePath;
+};
+
+std::unique_ptr<Module> parseModule(StringRef IR, LLVMContext &Ctx) {
+ SMDiagnostic Err;
+ std::unique_ptr<Module> M = parseAssemblyString(IR, Err, Ctx);
+ EXPECT_TRUE(M);
+ return M;
+}
+
+TEST(InstrumentorTest, RunsHookBeforeRuntimeLink) {
+ SmallString<128> RuntimePath;
+ int FD;
+ ASSERT_FALSE(sys::fs::createTemporaryFile("instrumentor-runtime", "ll", FD,
+ RuntimePath));
+ auto RemoveRuntime = make_scope_exit([&] { sys::fs::remove(RuntimePath); });
+
+ raw_fd_ostream OS(FD, true);
+ OS << "@runtime_marker = global i32 0\n";
+ OS.close();
+
+ LLVMContext Ctx;
+ std::unique_ptr<Module> M = parseModule(R"ir(
+ define void @test() {
+ entry:
+ ret void
+ }
+ )ir",
+ Ctx);
+ ASSERT_TRUE(M);
+
+ PreRuntimeLinkConfig Config(RuntimePath);
+ ModuleAnalysisManager MAM;
+ InstrumentorPass Pass(nullptr, &Config);
+ Pass.run(*M, MAM);
+
+ GlobalVariable *HookSawRuntime = M->getNamedGlobal("hook_saw_runtime");
+ ASSERT_NE(HookSawRuntime, nullptr);
+ auto *Initializer = dyn_cast<ConstantInt>(HookSawRuntime->getInitializer());
+ ASSERT_NE(Initializer, nullptr);
+ EXPECT_TRUE(Initializer->isZero());
+ EXPECT_NE(M->getNamedGlobal("runtime_marker"), nullptr);
+}
+
+} // namespace
More information about the llvm-commits
mailing list