[clang] [llvm] [clang-sycl-linker] Add per-kernel device code splitting (PR #195362)

Yury Plyakhin via cfe-commits cfe-commits at lists.llvm.org
Tue May 5 10:26:16 PDT 2026


================
@@ -461,6 +468,107 @@ static Error runAOTCompile(StringRef InputFile, StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+/// SYCL device code module split mode.
+enum IRSplitMode {
+  SPLIT_PER_KERNEL, // one module per kernel
+  SPLIT_NONE        // no splitting
+};
+
+/// Parses the value of \p -sycl-module-split-mode.
+static std::optional<IRSplitMode> convertStringToSplitMode(StringRef S) {
+  return StringSwitch<std::optional<IRSplitMode>>(S)
+      .Case("kernel", SPLIT_PER_KERNEL)
+      .Case("none", SPLIT_NONE)
+      .Default(std::nullopt);
+}
+
+/// Result of splitting a device module: the bitcode file path and the
+/// serialized symbol table for each device image.
+struct SplitModule {
+  SmallString<256> ModuleFilePath;
+  SmallString<0> Symbols;
+};
+
+static bool isEntryPoint(const Function &F) {
+  return !F.isDeclaration() && F.hasKernelCallingConv();
+}
+
+/// Collect kernel names from \p M and serialize them into a symbol table.
+static SmallString<0> collectSymbols(const Module &M) {
+  SmallVector<StringRef> KernelNames;
+  for (const Function &F : M)
+    if (isEntryPoint(F))
+      KernelNames.push_back(F.getName());
+  SmallString<0> SymbolData;
+  llvm::offloading::sycl::writeSymbolTable(KernelNames, SymbolData);
+  return SymbolData;
+}
+
+/// Splits the fully linked device \p M into one bitcode file per device image
+/// according to \p Mode and returns the list of split images with their symbol
+/// tables.
+///
+/// For SPLIT_NONE, \p LinkedBitcodeFile is returned as-is.
+/// For SPLIT_PER_KERNEL, the module is split into parts such that each part
+/// contains exactly one kernel entry point and its transitive dependencies;
+/// each part is written to a fresh temporary bitcode file.
+static Expected<SmallVector<SplitModule, 0>>
+splitDeviceCode(std::unique_ptr<Module> M, StringRef LinkedBitcodeFile,
+                IRSplitMode Mode, const ArgList &Args) {
+  SmallVector<SplitModule, 0> SplitModules;
+
+  if (Mode == SPLIT_NONE) {
+    SplitModules.push_back(
+        {SmallString<256>(LinkedBitcodeFile), collectSymbols(*M)});
+    return SplitModules;
+  }
+
+  assert(Mode == SPLIT_PER_KERNEL);
+
+  // splitModuleTransitiveFromEntryPoints asserts that at least one entry point
+  // was categorized. If the linked module contains no kernel definitions at
+  // all, there is nothing to split; fall back to shipping the linked module
+  // as a single image.
+  bool HasKernel = llvm::any_of(M->functions(), isEntryPoint);
+  if (!HasKernel) {
+    SplitModules.push_back(
+        {SmallString<256>(LinkedBitcodeFile), collectSymbols(*M)});
+    return SplitModules;
+  }
+
+  // Categorize each kernel function into its own group. Non-kernels and
+  // declarations return std::nullopt so they are pulled into whichever split
+  // transitively needs them.
+  int NextCategory = 0;
+  auto EntryPointCategorizer =
+      [&NextCategory](const Function &F) -> std::optional<int> {
+    if (!isEntryPoint(F))
+      return std::nullopt;
+    return NextCategory++;
+  };
+
+  if (Error Err = splitModuleTransitiveFromEntryPoints(
+          std::move(M), EntryPointCategorizer,
+          [&](std::unique_ptr<Module> Part) -> Error {
----------------
YuriPlyakhin wrote:

https://github.com/llvm/llvm-project/pull/195362/changes/33e58bf71528c90601dd9e36dec82a9534513bd1

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


More information about the cfe-commits mailing list