[llvm] [Instrumentor] Runtime bitcode linking controls (PR #216333)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 14 07:39:35 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Vincent Arcila (jandrovins)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/216333.diff


7 Files Affected:

- (modified) llvm/include/llvm/Transforms/IPO/Instrumentor.h (+28) 
- (modified) llvm/lib/Transforms/IPO/Instrumentor.cpp (+18-2) 
- (modified) llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp (+35) 
- (modified) llvm/test/Instrumentation/Instrumentor/default_config.json (+4-2) 
- (added) llvm/test/Instrumentation/Instrumentor/runtime_export_symbols.ll (+14) 
- (added) llvm/test/Instrumentation/Instrumentor/runtime_export_symbols_config.json (+15) 
- (added) llvm/test/Instrumentation/Instrumentor/runtimes/runtime_export_symbols_rt.ll (+14) 


``````````diff
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
index 5da53ac0ada15..915c3f3e35476 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..fc5b8a9845bef 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
+}
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

``````````

</details>


https://github.com/llvm/llvm-project/pull/216333


More information about the llvm-commits mailing list