[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-15 Thread Maksim Sabianin via cfe-commits

maksimsab wrote:

I don't have a right to give LGTM here but the PR looks good to me.

Note/idea: Alexey did mention in the past that `-dry-run` mode is designated 
for printing external commands which is not the case for `sycl-module-split` 
and `LLVM Backend`. Clang Driver has a capability for printing pipeline 
separately from printing external commands. We could reuse such approach/idea. 
Not in this PR but in the future.

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-12 Thread Alexey Bader via cfe-commits


@@ -468,84 +468,118 @@ static Error runAOTCompile(StringRef InputFile, 
StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
+
 /// SYCL device code module split mode.
 enum class IRSplitMode {
+  SPLIT_PER_TU, // one module per translation unit
   SPLIT_PER_KERNEL, // one module per kernel
   SPLIT_NONE// no splitting
 };
 
-/// Parses the value of \p -module-split-mode.
+/// Parses the value of \p --module-split-mode.
 static std::optional convertStringToSplitMode(StringRef S) {
   return StringSwitch>(S)
+  .Case("source", IRSplitMode::SPLIT_PER_TU)
   .Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
   .Case("none", IRSplitMode::SPLIT_NONE)
   .Default(std::nullopt);
 }
 
+static StringRef splitModeToString(IRSplitMode Mode) {
+  switch (Mode) {
+  case IRSplitMode::SPLIT_PER_TU:
+return "source";
+  case IRSplitMode::SPLIT_PER_KERNEL:
+return "kernel";
+  case IRSplitMode::SPLIT_NONE:
+return "none";
+  }
+  llvm_unreachable("bad split mode");
+}
+
 /// 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();
+static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) 
{
+  if (F.isDeclaration())
+return false;
+  if (F.hasKernelCallingConv())
+return true;
+  if (EmitOnlyKernelsAsEntryPoints)
+return false;
+  // sycl_external functions carry the "sycl-module-id" attribute.
+  // This branch is not reachable while EmitOnlyKernelsAsEntryPoints is
+  // hardcoded to true (see TODO in runSYCLLink).
+  return F.hasFnAttribute(AttrSYCLModuleId);
 }
 
-/// Collect kernel names from \p M and serialize them into a symbol table.
-static SmallString<0> collectSymbols(const Module &M) {
-  SmallVector KernelNames;
+/// Collect entry point names from \p M and serialize them into a symbol table.
+static SmallString<0> collectEntryPoints(const Module &M,
+ bool EmitOnlyKernelsAsEntryPoints) {
+  SmallVector Names;
   for (const Function &F : M)
-if (isEntryPoint(F))
-  KernelNames.push_back(F.getName());
+if (isEntryPoint(F, EmitOnlyKernelsAsEntryPoints))
+  Names.push_back(F.getName());
   SmallString<0> SymbolData;
-  llvm::offloading::sycl::writeSymbolTable(KernelNames, SymbolData);
+  llvm::offloading::sycl::writeSymbolTable(Names, 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>
-splitDeviceCode(std::unique_ptr M, StringRef LinkedBitcodeFile,
-IRSplitMode Mode, const ArgList &Args) {
-  SmallVector SplitModules;
+/// Functor passed to splitModuleTransitiveFromEntryPoints. For each input \p 
F,
+/// returns a numeric group ID (if \p F is an entry point) determining which
+/// device image it lands in, or std::nullopt (for non-entry-points).
+/// SPLIT_PER_KERNEL \p Mode gives each kernel its own ID;
+/// SPLIT_PER_TU \p Mode groups kernels by their "sycl-module-id" attribute
+/// value.
+class EntryPointCategorizer {
+public:
+  EntryPointCategorizer(IRSplitMode Mode, bool EmitOnlyKernelsAsEntryPoints)
+  : Mode(Mode), OnlyKernelsAreEntryPoints(EmitOnlyKernelsAsEntryPoints) {}
 
-  if (Mode == IRSplitMode::SPLIT_NONE) {
-SplitModules.push_back(
-{SmallString<256>(LinkedBitcodeFile), collectSymbols(*M)});
-return SplitModules;
-  }
+  std::optional operator()(const Function &F) {
+if (!isEntryPoint(F, OnlyKernelsAreEntryPoints))
+  return std::nullopt;
 
-  assert(Mode == IRSplitMode::SPLIT_PER_KERNEL);
+std::string Key;
+switch (Mode) {
+case IRSplitMode::SPLIT_PER_KERNEL:
+  Key = F.getName().str();
+  break;
+case IRSplitMode::SPLIT_PER_TU:
+  Key = F.getFnAttribute(AttrSYCLModuleId).getValueAsString().str();
+  break;
+case IRSplitMode::SPLIT_NONE:
+  llvm_unreachable("categorizer should not be used for SPLIT_NONE");

bader wrote:

```suggestion
  llvm_unreachable("categorizer cannot be used for SPLIT_NONE");
```
llvm_unreachable message _**should**_ be stronger than "should not". Please, 
use "cannot" instead of "should not".
Categorizer doesn't support `SPLIT_NONE`.

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

[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-10 Thread Alexey Bader via cfe-commits


@@ -3,13 +3,11 @@
 // REQUIRES: spirv-registered-target
 //
 // Test the dry run of a simple case to link two input files.
-// Also verifies the default split mode ("none").
 // RUN: %clangxx -emit-llvm -c -target spirv64 %s -o %t_1.bc

bader wrote:

Shouldn't we check the default "split mode"?

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-10 Thread Alexey Bader via cfe-commits


@@ -468,84 +468,119 @@ static Error runAOTCompile(StringRef InputFile, 
StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
+
 /// SYCL device code module split mode.
 enum class IRSplitMode {
+  SPLIT_PER_TU, // one module per translation unit
   SPLIT_PER_KERNEL, // one module per kernel
   SPLIT_NONE// no splitting
 };
 
-/// Parses the value of \p -module-split-mode.
+/// Parses the value of \p --module-split-mode.
 static std::optional convertStringToSplitMode(StringRef S) {
   return StringSwitch>(S)
+  .Case("source", IRSplitMode::SPLIT_PER_TU)
   .Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
   .Case("none", IRSplitMode::SPLIT_NONE)
   .Default(std::nullopt);
 }
 
+static StringRef splitModeToString(IRSplitMode Mode) {
+  switch (Mode) {
+  case IRSplitMode::SPLIT_PER_TU:
+return "source";
+  case IRSplitMode::SPLIT_PER_KERNEL:
+return "kernel";
+  case IRSplitMode::SPLIT_NONE:
+return "none";
+  }
+  llvm_unreachable("bad split mode");
+}
+
 /// 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();
+static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) 
{
+  if (F.isDeclaration())
+return false;
+  if (F.hasKernelCallingConv())
+return true;
+  if (EmitOnlyKernelsAsEntryPoints)
+return false;
+  // sycl_external functions carry the "sycl-module-id" attribute.
+  // This branch is not reachable while EmitOnlyKernelsAsEntryPoints is
+  // hardcoded to true (see TODO in runSYCLLink).
+  return F.hasFnAttribute(AttrSYCLModuleId);
 }
 
-/// Collect kernel names from \p M and serialize them into a symbol table.
-static SmallString<0> collectSymbols(const Module &M) {
-  SmallVector KernelNames;
+/// Collect entry point names from \p M and serialize them into a symbol table.
+static SmallString<0> collectSymbols(const Module &M,

bader wrote:

```suggestion
static SmallString<0> collectEntryPoints(const Module &M,
```
The comment clearly states that the function collects only entry points rather 
than all symbols.

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-10 Thread Alexey Bader via cfe-commits


@@ -75,11 +70,12 @@
 // RUN: | FileCheck %s --check-prefix=NOTARGET
 // NOTARGET: Target triple must be specified
 //
-// Test the split mode ("none"): no extra splits are produced.
-// RUN: clang-sycl-linker --dry-run -v -triple=spirv64 
--module-split-mode=none %t_1.bc %t_2.bc -o %t-split-none.out 2>&1 \
+// Test the split mode ("none"): kernels from different TUs are not split into
+// separate images.

bader wrote:

A few test improvement suggestions.

If the intention to separate all tests for "split mode", I suggest we create a 
separate file for it (e.g. 
`clang/test/Driver/clang-sycl-linker-split-mode.cpp`).

I would drop the "-test" suffix from the test name: 
"`clang/test/Driver/clang-sycl-linker-test.cpp`" -> 
"`clang/test/Driver/clang-sycl-linker.cpp`". It doesn't seem to be useful.

Finally, consider moving clang-sycl-linker tests from the Driver to Tooling 
directory. There tests can be written in LLVM, so you can convert Input *.ll 
files into tests (i.e. no need to create a separate *.cpp file for LIT 
commands).

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-10 Thread Alexey Bader via cfe-commits


@@ -57,6 +57,9 @@ def opencl_aot_options_EQ : Joined<["--", "-"], 
"opencl-aot-options=">,
 
 def module_split_mode_EQ : Joined<["--", "-"], "module-split-mode=">,
   Flags<[LinkerOnlyOption]>, MetaVarName<"">,
-  HelpText<"SYCL device code module split mode. Valid values: 'none' (default) 
"
-   "emits a single device image; 'kernel' emits one device image per "
-   "kernel function.">;
+  HelpText<"SYCL device code module split mode. Valid values: "
+   "'source' (default) emits one device image per translation unit 
that contains "
+   "at least one kernel; translation units containing only 
sycl_external "
+   "functions do not produce a device image, this behavior may change 
in the future; "

bader wrote:

> translation units containing only sycl_external functions do not produce a 
> device image, this behavior may change in the future

What does it mean? I suggest we drop this part.


```suggestion
   "at least one entry point"
```

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-10 Thread Alexey Bader via cfe-commits


@@ -560,17 +595,38 @@ splitDeviceCode(std::unique_ptr M, StringRef 
LinkedBitcodeFile,
 WriteBitcodeToFile(*Part, OS);
 
 SplitModules.push_back(
-{SmallString<256>(*BitcodeFileOrErr), collectSymbols(*Part)});
+{SmallString<256>(*BitcodeFileOrErr),
+ collectSymbols(*Part, EmitOnlyKernelsAsEntryPoints)});
 return Error::success();
   };
 
   if (Error Err = splitModuleTransitiveFromEntryPoints(
-  std::move(M), EntryPointCategorizer, SplitCallback))
+  std::move(M), Categorizer, SplitCallback))
 return Err;
 
+  if (Verbose || DryRun) {
+SmallVector SplitFiles;
+for (const SplitModule &SI : SplitModules)
+  SplitFiles.push_back(SI.ModuleFilePath);
+errs() << formatv("sycl-module-split: input: {0}, output: {1}, mode: 
{2}\n",
+  LinkedBitcodeFile, llvm::join(SplitFiles, ", "),
+  splitModeToString(Mode));
+  }
+
   return SplitModules;
 }
 
+/// Returns true if module splitting can be skipped: either \p Mode is
+/// SPLIT_NONE, or \p M contains no entry points (nothing to split from).
+static bool checkModuleSplitCanBeSkipped(IRSplitMode Mode, const Module &M,
+ bool EmitOnlyKernelsAsEntryPoints) {
+  if (Mode == IRSplitMode::SPLIT_NONE)
+return true;
+  return !llvm::any_of(M.functions(), [&](const Function &F) {

bader wrote:

```suggestion
  return llvm::none_of(M.functions(), [&](const Function &F) {
```

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-10 Thread Alexey Bader via cfe-commits


@@ -468,84 +468,119 @@ static Error runAOTCompile(StringRef InputFile, 
StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
+
 /// SYCL device code module split mode.
 enum class IRSplitMode {
+  SPLIT_PER_TU, // one module per translation unit
   SPLIT_PER_KERNEL, // one module per kernel
   SPLIT_NONE// no splitting
 };
 
-/// Parses the value of \p -module-split-mode.
+/// Parses the value of \p --module-split-mode.
 static std::optional convertStringToSplitMode(StringRef S) {
   return StringSwitch>(S)
+  .Case("source", IRSplitMode::SPLIT_PER_TU)
   .Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
   .Case("none", IRSplitMode::SPLIT_NONE)
   .Default(std::nullopt);
 }
 
+static StringRef splitModeToString(IRSplitMode Mode) {
+  switch (Mode) {
+  case IRSplitMode::SPLIT_PER_TU:
+return "source";
+  case IRSplitMode::SPLIT_PER_KERNEL:
+return "kernel";
+  case IRSplitMode::SPLIT_NONE:
+return "none";
+  }
+  llvm_unreachable("bad split mode");
+}
+
 /// 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();
+static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) 
{
+  if (F.isDeclaration())
+return false;
+  if (F.hasKernelCallingConv())
+return true;
+  if (EmitOnlyKernelsAsEntryPoints)
+return false;
+  // sycl_external functions carry the "sycl-module-id" attribute.
+  // This branch is not reachable while EmitOnlyKernelsAsEntryPoints is
+  // hardcoded to true (see TODO in runSYCLLink).
+  return F.hasFnAttribute(AttrSYCLModuleId);
 }
 
-/// Collect kernel names from \p M and serialize them into a symbol table.
-static SmallString<0> collectSymbols(const Module &M) {
-  SmallVector KernelNames;
+/// Collect entry point names from \p M and serialize them into a symbol table.
+static SmallString<0> collectSymbols(const Module &M,
+ bool EmitOnlyKernelsAsEntryPoints) {
+  SmallVector Names;
   for (const Function &F : M)
-if (isEntryPoint(F))
-  KernelNames.push_back(F.getName());
+if (isEntryPoint(F, EmitOnlyKernelsAsEntryPoints))
+  Names.push_back(F.getName());
   SmallString<0> SymbolData;
-  llvm::offloading::sycl::writeSymbolTable(KernelNames, SymbolData);
+  llvm::offloading::sycl::writeSymbolTable(Names, 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>
-splitDeviceCode(std::unique_ptr M, StringRef LinkedBitcodeFile,
-IRSplitMode Mode, const ArgList &Args) {
-  SmallVector SplitModules;
+/// Functor passed to splitModuleTransitiveFromEntryPoints. For each input \p 
F,
+/// returns a numeric group ID (if \p F is an entry point) determining which
+/// device image it lands in, or std::nullopt (for non-entry-points).
+/// SPLIT_PER_KERNEL \p Mode gives each kernel its own ID;
+/// SPLIT_PER_TU \p Mode groups kernels by their "sycl-module-id" attribute
+/// value.
+class EntryPointCategorizer {
+public:
+  EntryPointCategorizer(IRSplitMode Mode, bool EmitOnlyKernelsAsEntryPoints)
+  : Mode(Mode), OnlyKernelsAreEntryPoints(EmitOnlyKernelsAsEntryPoints) {}
 
-  if (Mode == IRSplitMode::SPLIT_NONE) {
-SplitModules.push_back(
-{SmallString<256>(LinkedBitcodeFile), collectSymbols(*M)});
-return SplitModules;
-  }
+  std::optional operator()(const Function &F) {
+if (!isEntryPoint(F, OnlyKernelsAreEntryPoints))
+  return std::nullopt;
 
-  assert(Mode == IRSplitMode::SPLIT_PER_KERNEL);
+std::string Key;
+switch (Mode) {
+case IRSplitMode::SPLIT_PER_KERNEL:
+  Key = F.getName().str();
+  break;
+case IRSplitMode::SPLIT_PER_TU:
+  Key = F.getFnAttribute(AttrSYCLModuleId).getValueAsString().str();
+  break;
+case IRSplitMode::SPLIT_NONE:
+  llvm_unreachable("categorizer not used for SPLIT_NONE");
+}
 
-  // 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) {
-SplitModule

[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-10 Thread Alexey Bader via cfe-commits


@@ -468,84 +468,119 @@ static Error runAOTCompile(StringRef InputFile, 
StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
+
 /// SYCL device code module split mode.
 enum class IRSplitMode {
+  SPLIT_PER_TU, // one module per translation unit
   SPLIT_PER_KERNEL, // one module per kernel
   SPLIT_NONE// no splitting
 };
 
-/// Parses the value of \p -module-split-mode.
+/// Parses the value of \p --module-split-mode.
 static std::optional convertStringToSplitMode(StringRef S) {
   return StringSwitch>(S)
+  .Case("source", IRSplitMode::SPLIT_PER_TU)
   .Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
   .Case("none", IRSplitMode::SPLIT_NONE)
   .Default(std::nullopt);
 }
 
+static StringRef splitModeToString(IRSplitMode Mode) {
+  switch (Mode) {
+  case IRSplitMode::SPLIT_PER_TU:
+return "source";
+  case IRSplitMode::SPLIT_PER_KERNEL:
+return "kernel";
+  case IRSplitMode::SPLIT_NONE:
+return "none";
+  }
+  llvm_unreachable("bad split mode");
+}
+
 /// 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();
+static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) 
{
+  if (F.isDeclaration())
+return false;
+  if (F.hasKernelCallingConv())
+return true;
+  if (EmitOnlyKernelsAsEntryPoints)
+return false;
+  // sycl_external functions carry the "sycl-module-id" attribute.
+  // This branch is not reachable while EmitOnlyKernelsAsEntryPoints is
+  // hardcoded to true (see TODO in runSYCLLink).
+  return F.hasFnAttribute(AttrSYCLModuleId);
 }
 
-/// Collect kernel names from \p M and serialize them into a symbol table.
-static SmallString<0> collectSymbols(const Module &M) {
-  SmallVector KernelNames;
+/// Collect entry point names from \p M and serialize them into a symbol table.
+static SmallString<0> collectSymbols(const Module &M,
+ bool EmitOnlyKernelsAsEntryPoints) {
+  SmallVector Names;
   for (const Function &F : M)
-if (isEntryPoint(F))
-  KernelNames.push_back(F.getName());
+if (isEntryPoint(F, EmitOnlyKernelsAsEntryPoints))
+  Names.push_back(F.getName());
   SmallString<0> SymbolData;
-  llvm::offloading::sycl::writeSymbolTable(KernelNames, SymbolData);
+  llvm::offloading::sycl::writeSymbolTable(Names, 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>
-splitDeviceCode(std::unique_ptr M, StringRef LinkedBitcodeFile,
-IRSplitMode Mode, const ArgList &Args) {
-  SmallVector SplitModules;
+/// Functor passed to splitModuleTransitiveFromEntryPoints. For each input \p 
F,
+/// returns a numeric group ID (if \p F is an entry point) determining which
+/// device image it lands in, or std::nullopt (for non-entry-points).
+/// SPLIT_PER_KERNEL \p Mode gives each kernel its own ID;
+/// SPLIT_PER_TU \p Mode groups kernels by their "sycl-module-id" attribute
+/// value.
+class EntryPointCategorizer {
+public:
+  EntryPointCategorizer(IRSplitMode Mode, bool EmitOnlyKernelsAsEntryPoints)
+  : Mode(Mode), OnlyKernelsAreEntryPoints(EmitOnlyKernelsAsEntryPoints) {}
 
-  if (Mode == IRSplitMode::SPLIT_NONE) {
-SplitModules.push_back(
-{SmallString<256>(LinkedBitcodeFile), collectSymbols(*M)});
-return SplitModules;
-  }
+  std::optional operator()(const Function &F) {
+if (!isEntryPoint(F, OnlyKernelsAreEntryPoints))
+  return std::nullopt;
 
-  assert(Mode == IRSplitMode::SPLIT_PER_KERNEL);
+std::string Key;
+switch (Mode) {
+case IRSplitMode::SPLIT_PER_KERNEL:
+  Key = F.getName().str();
+  break;
+case IRSplitMode::SPLIT_PER_TU:
+  Key = F.getFnAttribute(AttrSYCLModuleId).getValueAsString().str();
+  break;
+case IRSplitMode::SPLIT_NONE:
+  llvm_unreachable("categorizer not used for SPLIT_NONE");

bader wrote:

What does the message mean? I think if we get here categorize **is** used for 
`SPLIT_NONE` mode. 
Should it say, "categorizer doesn't support SPLIT_NONE" or "categorizer can't 
be used for SPLIT_NONE"?

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
cfe-commits@lis

[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-10 Thread Alexey Bader via cfe-commits


@@ -0,0 +1,25 @@
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"

bader wrote:

Please, combine these two inputs into one. It would be more efficient to 
validate.

We should be able to verify `--module-split-mode=source` with a single tool 
invocation.

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-09 Thread Yury Plyakhin via cfe-commits


@@ -57,6 +57,11 @@ def opencl_aot_options_EQ : Joined<["--", "-"], 
"opencl-aot-options=">,
 
 def module_split_mode_EQ : Joined<["--", "-"], "module-split-mode=">,
   Flags<[LinkerOnlyOption]>, MetaVarName<"">,
-  HelpText<"SYCL device code module split mode. Valid values: 'none' (default) 
"
-   "emits a single device image; 'kernel' emits one device image per "
-   "kernel function.">;
+  HelpText<"SYCL device code module split mode. Valid values: "
+   "'source' (default) emits one device image per translation unit "
+   "that contains at least one kernel (grouped by the 'sycl-module-id' 
"
+   "attribute); translation units containing only sycl_external "

YuriPlyakhin wrote:

39f9343ac0df15b2789075f192c58925cc5d4879

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-09 Thread Yury Plyakhin via cfe-commits

https://github.com/YuriPlyakhin updated 
https://github.com/llvm/llvm-project/pull/196435

>From de5f17e5602c93d3aa6814078ee216dcf3687ece Mon Sep 17 00:00:00 2001
From: "Plyakhin, Yury" 
Date: Wed, 6 May 2026 18:32:36 +0200
Subject: [PATCH 1/2] [clang-sycl-linker] Add per-translation-unit device code
 split mode

Adds `source` split mode to `clang-sycl-linker`, driven by the
`sycl-module-id` function attribute emitted by the SYCL frontend.
`source` is the default mode and groups kernels by the value of their
`sycl-module-id` attribute, emitting one device image per translation
unit. If the linked module contains no kernels, no device image is
emitted. `none` disables splitting and emits a single device image.
`kernel` emits one device image per kernel function.

The `EntryPointCategorizer` in `ClangSYCLLinker.cpp` is refactored into a
class (instead of a stateful lambda) to support both per-kernel and per-TU
modes cleanly.

`llvm-split`'s `-split-by-category=module-id` is renamed to
`-split-by-category=attribute` and the previously hardcoded `"module-id"`
attribute name is replaced by a required `--category-attribute=` CLI
option. This decouples the tool from any specific attribute name. All
`SplitByCategory` tests are updated accordingly.

Co-Authored-By: Claude 
---
 clang/test/Driver/Inputs/SYCL/external-fn.ll  |  19 +++
 clang/test/Driver/Inputs/SYCL/two-modules.ll  |  25 
 clang/test/Driver/clang-sycl-linker-test.cpp  |  32 -
 .../clang-sycl-linker/ClangSYCLLinker.cpp | 131 --
 clang/tools/clang-sycl-linker/SYCLLinkOpts.td |  11 +-
 .../complex-indirect-call-chain1.ll   |   2 +-
 .../complex-indirect-call-chain2.ll   |   2 +-
 .../SplitByCategory/module-split-func-ptr.ll  |   2 +-
 .../SplitByCategory/split-by-source.ll|   2 +-
 .../split-with-kernel-declarations.ll |   2 +-
 llvm/tools/llvm-split/llvm-split.cpp  |  47 ---
 11 files changed, 205 insertions(+), 70 deletions(-)
 create mode 100644 clang/test/Driver/Inputs/SYCL/external-fn.ll
 create mode 100644 clang/test/Driver/Inputs/SYCL/two-modules.ll

diff --git a/clang/test/Driver/Inputs/SYCL/external-fn.ll 
b/clang/test/Driver/Inputs/SYCL/external-fn.ll
new file mode 100644
index 0..b6ec0de46bdad
--- /dev/null
+++ b/clang/test/Driver/Inputs/SYCL/external-fn.ll
@@ -0,0 +1,19 @@
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+; A kernel from TU1 and a sycl_external function from TU2.
+
+define spir_func i32 @ext_fn(i32 %a) #1 {
+entry:
+  %r = add nsw i32 %a, 2
+  ret i32 %r
+}
+
+define spir_kernel void @k(ptr addrspace(1) %out) #0 {
+entry:
+  store i32 42, ptr addrspace(1) %out, align 4
+  ret void
+}
+
+attributes #0 = { "sycl-module-id"="TU1.cpp" }
+attributes #1 = { "sycl-module-id"="TU2.cpp" }
diff --git a/clang/test/Driver/Inputs/SYCL/two-modules.ll 
b/clang/test/Driver/Inputs/SYCL/two-modules.ll
new file mode 100644
index 0..d63f0e6f38726
--- /dev/null
+++ b/clang/test/Driver/Inputs/SYCL/two-modules.ll
@@ -0,0 +1,25 @@
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @helper(i32 %a) {
+entry:
+  %r = add nsw i32 %a, 1
+  ret i32 %r
+}
+
+define spir_kernel void @kernel_a(ptr addrspace(1) %out, i32 %a) #0 {
+entry:
+  %r = call spir_func i32 @helper(i32 %a)
+  store i32 %r, ptr addrspace(1) %out, align 4
+  ret void
+}
+
+define spir_kernel void @kernel_b(ptr addrspace(1) %out, i32 %a) #1 {
+entry:
+  %r = call spir_func i32 @helper(i32 %a)
+  store i32 %r, ptr addrspace(1) %out, align 4
+  ret void
+}
+
+attributes #0 = { "sycl-module-id"="TU1.cpp" }
+attributes #1 = { "sycl-module-id"="TU2.cpp" }
diff --git a/clang/test/Driver/clang-sycl-linker-test.cpp 
b/clang/test/Driver/clang-sycl-linker-test.cpp
index cd99d4d47b1e1..69596252efdf0 100644
--- a/clang/test/Driver/clang-sycl-linker-test.cpp
+++ b/clang/test/Driver/clang-sycl-linker-test.cpp
@@ -3,13 +3,14 @@
 // REQUIRES: spirv-registered-target
 //
 // Test the dry run of a simple case to link two input files.
-// Also verifies the default split mode ("none").
+// The input has no SYCL kernels, so the default split mode ('source') produces
+// a single device image via the no-entry-point fallback.
 // RUN: %clangxx -emit-llvm -c -target spirv64 %s -o %t_1.bc
 // RUN: %clangxx -emit-llvm -c -target spirv64 %s -o %t_2.bc
 // RUN: clang-sycl-linker --dry-run -v -triple=spirv64 %t_1.bc %t_2.bc -o 
%t-spirv.out 2>&1 \
 // RUN:   | FileCheck %s --check-prefix=SIMPLE-FO
 // SIMPLE-FO:  sycl-device-link: inputs: {{.*}}.bc, {{.*}}.bc  libfiles:  
output: [[LLVMLINKOUT:.*]].bc
-// SIMPLE-FO-NEXT: sycl-module-split: input: [[LLVMLINKOUT]].bc, output: 
[[LLVMLINKOUT]].bc, mode: none
+// SIMPLE-FO-NEXT: sycl-module-split: input: [[LLVMLINKOUT]].bc, output: 
[[LLVMLINKOUT]].bc,

[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-09 Thread Yury Plyakhin via cfe-commits


@@ -468,84 +468,129 @@ static Error runAOTCompile(StringRef InputFile, 
StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
+
 /// SYCL device code module split mode.
 enum class IRSplitMode {
+  SPLIT_PER_TU, // one module per translation unit
   SPLIT_PER_KERNEL, // one module per kernel
   SPLIT_NONE// no splitting
 };
 
-/// Parses the value of \p -module-split-mode.
+/// Parses the value of \p --module-split-mode.
 static std::optional convertStringToSplitMode(StringRef S) {
   return StringSwitch>(S)
+  .Case("source", IRSplitMode::SPLIT_PER_TU)
   .Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
   .Case("none", IRSplitMode::SPLIT_NONE)
   .Default(std::nullopt);
 }
 
+static StringRef splitModeToString(IRSplitMode Mode) {
+  switch (Mode) {
+  case IRSplitMode::SPLIT_PER_TU:
+return "source";
+  case IRSplitMode::SPLIT_PER_KERNEL:
+return "kernel";
+  case IRSplitMode::SPLIT_NONE:
+return "none";
+  }
+  llvm_unreachable("bad split mode");
+}
+
 /// 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();
+static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) 
{
+  if (F.isDeclaration())
+return false;
+  if (F.hasKernelCallingConv())
+return true;
+  if (EmitOnlyKernelsAsEntryPoints)
+return false;
+  // sycl_external functions carry the "sycl-module-id" attribute.
+  return F.hasFnAttribute(AttrSYCLModuleId);
 }
 
-/// Collect kernel names from \p M and serialize them into a symbol table.
-static SmallString<0> collectSymbols(const Module &M) {
-  SmallVector KernelNames;
+/// Collect entry point names from \p M and serialize them into a symbol table.
+static SmallString<0> collectSymbols(const Module &M,
+ bool EmitOnlyKernelsAsEntryPoints) {
+  SmallVector Names;
   for (const Function &F : M)
-if (isEntryPoint(F))
-  KernelNames.push_back(F.getName());
+if (isEntryPoint(F, EmitOnlyKernelsAsEntryPoints))
+  Names.push_back(F.getName());
   SmallString<0> SymbolData;
-  llvm::offloading::sycl::writeSymbolTable(KernelNames, SymbolData);
+  llvm::offloading::sycl::writeSymbolTable(Names, SymbolData);
   return SymbolData;
 }
 
+class EntryPointCategorizer {
+public:
+  EntryPointCategorizer(IRSplitMode Mode, bool EmitOnlyKernelsAsEntryPoints)
+  : Mode(Mode), OnlyKernelsAreEntryPoints(EmitOnlyKernelsAsEntryPoints) {}
+
+  std::optional operator()(const Function &F) {

YuriPlyakhin wrote:

39f9343ac0df15b2789075f192c58925cc5d4879

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-09 Thread Yury Plyakhin via cfe-commits

https://github.com/YuriPlyakhin edited 
https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-09 Thread Yury Plyakhin via cfe-commits

https://github.com/YuriPlyakhin updated 
https://github.com/llvm/llvm-project/pull/196435

>From de5f17e5602c93d3aa6814078ee216dcf3687ece Mon Sep 17 00:00:00 2001
From: "Plyakhin, Yury" 
Date: Wed, 6 May 2026 18:32:36 +0200
Subject: [PATCH 1/2] [clang-sycl-linker] Add per-translation-unit device code
 split mode

Adds `source` split mode to `clang-sycl-linker`, driven by the
`sycl-module-id` function attribute emitted by the SYCL frontend.
`source` is the default mode and groups kernels by the value of their
`sycl-module-id` attribute, emitting one device image per translation
unit. If the linked module contains no kernels, no device image is
emitted. `none` disables splitting and emits a single device image.
`kernel` emits one device image per kernel function.

The `EntryPointCategorizer` in `ClangSYCLLinker.cpp` is refactored into a
class (instead of a stateful lambda) to support both per-kernel and per-TU
modes cleanly.

`llvm-split`'s `-split-by-category=module-id` is renamed to
`-split-by-category=attribute` and the previously hardcoded `"module-id"`
attribute name is replaced by a required `--category-attribute=` CLI
option. This decouples the tool from any specific attribute name. All
`SplitByCategory` tests are updated accordingly.

Co-Authored-By: Claude 
---
 clang/test/Driver/Inputs/SYCL/external-fn.ll  |  19 +++
 clang/test/Driver/Inputs/SYCL/two-modules.ll  |  25 
 clang/test/Driver/clang-sycl-linker-test.cpp  |  32 -
 .../clang-sycl-linker/ClangSYCLLinker.cpp | 131 --
 clang/tools/clang-sycl-linker/SYCLLinkOpts.td |  11 +-
 .../complex-indirect-call-chain1.ll   |   2 +-
 .../complex-indirect-call-chain2.ll   |   2 +-
 .../SplitByCategory/module-split-func-ptr.ll  |   2 +-
 .../SplitByCategory/split-by-source.ll|   2 +-
 .../split-with-kernel-declarations.ll |   2 +-
 llvm/tools/llvm-split/llvm-split.cpp  |  47 ---
 11 files changed, 205 insertions(+), 70 deletions(-)
 create mode 100644 clang/test/Driver/Inputs/SYCL/external-fn.ll
 create mode 100644 clang/test/Driver/Inputs/SYCL/two-modules.ll

diff --git a/clang/test/Driver/Inputs/SYCL/external-fn.ll 
b/clang/test/Driver/Inputs/SYCL/external-fn.ll
new file mode 100644
index 0..b6ec0de46bdad
--- /dev/null
+++ b/clang/test/Driver/Inputs/SYCL/external-fn.ll
@@ -0,0 +1,19 @@
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+; A kernel from TU1 and a sycl_external function from TU2.
+
+define spir_func i32 @ext_fn(i32 %a) #1 {
+entry:
+  %r = add nsw i32 %a, 2
+  ret i32 %r
+}
+
+define spir_kernel void @k(ptr addrspace(1) %out) #0 {
+entry:
+  store i32 42, ptr addrspace(1) %out, align 4
+  ret void
+}
+
+attributes #0 = { "sycl-module-id"="TU1.cpp" }
+attributes #1 = { "sycl-module-id"="TU2.cpp" }
diff --git a/clang/test/Driver/Inputs/SYCL/two-modules.ll 
b/clang/test/Driver/Inputs/SYCL/two-modules.ll
new file mode 100644
index 0..d63f0e6f38726
--- /dev/null
+++ b/clang/test/Driver/Inputs/SYCL/two-modules.ll
@@ -0,0 +1,25 @@
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @helper(i32 %a) {
+entry:
+  %r = add nsw i32 %a, 1
+  ret i32 %r
+}
+
+define spir_kernel void @kernel_a(ptr addrspace(1) %out, i32 %a) #0 {
+entry:
+  %r = call spir_func i32 @helper(i32 %a)
+  store i32 %r, ptr addrspace(1) %out, align 4
+  ret void
+}
+
+define spir_kernel void @kernel_b(ptr addrspace(1) %out, i32 %a) #1 {
+entry:
+  %r = call spir_func i32 @helper(i32 %a)
+  store i32 %r, ptr addrspace(1) %out, align 4
+  ret void
+}
+
+attributes #0 = { "sycl-module-id"="TU1.cpp" }
+attributes #1 = { "sycl-module-id"="TU2.cpp" }
diff --git a/clang/test/Driver/clang-sycl-linker-test.cpp 
b/clang/test/Driver/clang-sycl-linker-test.cpp
index cd99d4d47b1e1..69596252efdf0 100644
--- a/clang/test/Driver/clang-sycl-linker-test.cpp
+++ b/clang/test/Driver/clang-sycl-linker-test.cpp
@@ -3,13 +3,14 @@
 // REQUIRES: spirv-registered-target
 //
 // Test the dry run of a simple case to link two input files.
-// Also verifies the default split mode ("none").
+// The input has no SYCL kernels, so the default split mode ('source') produces
+// a single device image via the no-entry-point fallback.
 // RUN: %clangxx -emit-llvm -c -target spirv64 %s -o %t_1.bc
 // RUN: %clangxx -emit-llvm -c -target spirv64 %s -o %t_2.bc
 // RUN: clang-sycl-linker --dry-run -v -triple=spirv64 %t_1.bc %t_2.bc -o 
%t-spirv.out 2>&1 \
 // RUN:   | FileCheck %s --check-prefix=SIMPLE-FO
 // SIMPLE-FO:  sycl-device-link: inputs: {{.*}}.bc, {{.*}}.bc  libfiles:  
output: [[LLVMLINKOUT:.*]].bc
-// SIMPLE-FO-NEXT: sycl-module-split: input: [[LLVMLINKOUT]].bc, output: 
[[LLVMLINKOUT]].bc, mode: none
+// SIMPLE-FO-NEXT: sycl-module-split: input: [[LLVMLINKOUT]].bc, output: 
[[LLVMLINKOUT]].bc,

[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-09 Thread Yury Plyakhin via cfe-commits


@@ -468,84 +468,129 @@ static Error runAOTCompile(StringRef InputFile, 
StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
+
 /// SYCL device code module split mode.
 enum class IRSplitMode {
+  SPLIT_PER_TU, // one module per translation unit
   SPLIT_PER_KERNEL, // one module per kernel
   SPLIT_NONE// no splitting
 };
 
-/// Parses the value of \p -module-split-mode.
+/// Parses the value of \p --module-split-mode.
 static std::optional convertStringToSplitMode(StringRef S) {
   return StringSwitch>(S)
+  .Case("source", IRSplitMode::SPLIT_PER_TU)
   .Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
   .Case("none", IRSplitMode::SPLIT_NONE)
   .Default(std::nullopt);
 }
 
+static StringRef splitModeToString(IRSplitMode Mode) {
+  switch (Mode) {
+  case IRSplitMode::SPLIT_PER_TU:
+return "source";
+  case IRSplitMode::SPLIT_PER_KERNEL:
+return "kernel";
+  case IRSplitMode::SPLIT_NONE:
+return "none";
+  }
+  llvm_unreachable("bad split mode");
+}
+
 /// 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();
+static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) 
{
+  if (F.isDeclaration())
+return false;
+  if (F.hasKernelCallingConv())
+return true;
+  if (EmitOnlyKernelsAsEntryPoints)

YuriPlyakhin wrote:

only one branch. I added comment: 39f9343ac0df15b2789075f192c58925cc5d4879

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-09 Thread Yury Plyakhin via cfe-commits

YuriPlyakhin wrote:

> nit:
> 
> ```c++
> return createStringError(inconvertibleErrorCode(), "error message");
> ```
> 
> can be shortened to
> 
> ```c++
> return createStringError("error message");
> ```

I see this in several places, none of which is touched by this PR, I'll address 
this as an [NFC] refactoring in a new PR to not pollute this one.

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-09 Thread Yury Plyakhin via cfe-commits


@@ -468,84 +468,129 @@ static Error runAOTCompile(StringRef InputFile, 
StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
+
 /// SYCL device code module split mode.
 enum class IRSplitMode {
+  SPLIT_PER_TU, // one module per translation unit
   SPLIT_PER_KERNEL, // one module per kernel
   SPLIT_NONE// no splitting
 };
 
-/// Parses the value of \p -module-split-mode.
+/// Parses the value of \p --module-split-mode.
 static std::optional convertStringToSplitMode(StringRef S) {
   return StringSwitch>(S)
+  .Case("source", IRSplitMode::SPLIT_PER_TU)
   .Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
   .Case("none", IRSplitMode::SPLIT_NONE)
   .Default(std::nullopt);
 }
 
+static StringRef splitModeToString(IRSplitMode Mode) {
+  switch (Mode) {
+  case IRSplitMode::SPLIT_PER_TU:
+return "source";
+  case IRSplitMode::SPLIT_PER_KERNEL:
+return "kernel";
+  case IRSplitMode::SPLIT_NONE:
+return "none";
+  }
+  llvm_unreachable("bad split mode");
+}
+
 /// 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();
+static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) 
{
+  if (F.isDeclaration())
+return false;
+  if (F.hasKernelCallingConv())
+return true;
+  if (EmitOnlyKernelsAsEntryPoints)
+return false;
+  // sycl_external functions carry the "sycl-module-id" attribute.
+  return F.hasFnAttribute(AttrSYCLModuleId);
 }
 
-/// Collect kernel names from \p M and serialize them into a symbol table.
-static SmallString<0> collectSymbols(const Module &M) {
-  SmallVector KernelNames;
+/// Collect entry point names from \p M and serialize them into a symbol table.
+static SmallString<0> collectSymbols(const Module &M,
+ bool EmitOnlyKernelsAsEntryPoints) {
+  SmallVector Names;
   for (const Function &F : M)
-if (isEntryPoint(F))
-  KernelNames.push_back(F.getName());
+if (isEntryPoint(F, EmitOnlyKernelsAsEntryPoints))
+  Names.push_back(F.getName());
   SmallString<0> SymbolData;
-  llvm::offloading::sycl::writeSymbolTable(KernelNames, SymbolData);
+  llvm::offloading::sycl::writeSymbolTable(Names, SymbolData);
   return SymbolData;
 }
 
+class EntryPointCategorizer {
+public:
+  EntryPointCategorizer(IRSplitMode Mode, bool EmitOnlyKernelsAsEntryPoints)
+  : Mode(Mode), OnlyKernelsAreEntryPoints(EmitOnlyKernelsAsEntryPoints) {}
+
+  std::optional operator()(const Function &F) {
+if (!isEntryPoint(F, OnlyKernelsAreEntryPoints))
+  return std::nullopt;
+
+std::string Key;
+switch (Mode) {
+case IRSplitMode::SPLIT_PER_KERNEL:
+  Key = F.getName().str();
+  break;
+case IRSplitMode::SPLIT_PER_TU:
+  Key = F.getFnAttribute(AttrSYCLModuleId).getValueAsString().str();
+  break;
+case IRSplitMode::SPLIT_NONE:
+  llvm_unreachable("categorizer not used for SPLIT_NONE");
+}
+
+auto [It, Inserted] =
+StrToId.try_emplace(std::move(Key), static_cast(StrToId.size()));
+return It->second;
+  }
+
+private:
+  IRSplitMode Mode;
+  bool OnlyKernelsAreEntryPoints;
+  llvm::StringMap StrToId;
+};
+
 /// 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.

YuriPlyakhin wrote:

39f9343ac0df15b2789075f192c58925cc5d4879

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-08 Thread Maksim Sabianin via cfe-commits


@@ -468,84 +468,129 @@ static Error runAOTCompile(StringRef InputFile, 
StringRef OutputFile,
   return createStringError(inconvertibleErrorCode(), "Unsupported arch");
 }
 
+static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
+
 /// SYCL device code module split mode.
 enum class IRSplitMode {
+  SPLIT_PER_TU, // one module per translation unit
   SPLIT_PER_KERNEL, // one module per kernel
   SPLIT_NONE// no splitting
 };
 
-/// Parses the value of \p -module-split-mode.
+/// Parses the value of \p --module-split-mode.
 static std::optional convertStringToSplitMode(StringRef S) {
   return StringSwitch>(S)
+  .Case("source", IRSplitMode::SPLIT_PER_TU)
   .Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
   .Case("none", IRSplitMode::SPLIT_NONE)
   .Default(std::nullopt);
 }
 
+static StringRef splitModeToString(IRSplitMode Mode) {
+  switch (Mode) {
+  case IRSplitMode::SPLIT_PER_TU:
+return "source";
+  case IRSplitMode::SPLIT_PER_KERNEL:
+return "kernel";
+  case IRSplitMode::SPLIT_NONE:
+return "none";
+  }
+  llvm_unreachable("bad split mode");
+}
+
 /// 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();
+static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) 
{
+  if (F.isDeclaration())
+return false;
+  if (F.hasKernelCallingConv())
+return true;
+  if (EmitOnlyKernelsAsEntryPoints)

maksimsab wrote:

Is this tested?

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [llvm] [clang-sycl-linker] Add per-translation-unit device code split mode (PR #196435)

2026-05-08 Thread Maksim Sabianin via cfe-commits

maksimsab wrote:

nit:
```cpp
return createStringError(inconvertibleErrorCode(), "error message");
```

can be shortened to
```cpp
return createStringError("error message");
```

https://github.com/llvm/llvm-project/pull/196435
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits