[llvm] [llvm-calc-occupancy] Add an AMDGPU occupancy calculator tool (PR #208727)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 13 00:58:20 PDT 2026


https://github.com/michaelselehov updated https://github.com/llvm/llvm-project/pull/208727

>From 51447e07817c0a2c9653769610a51ea3230bbf64 Mon Sep 17 00:00:00 2001
From: mselehov <mselehov at amd.com>
Date: Fri, 10 Jul 2026 08:42:15 -0500
Subject: [PATCH 1/3] [llvm-calc-occupancy] Add an AMDGPU occupancy calculator
 tool

Add a command-line utility that reports the occupancy (waves/EU) an AMDGPU
kernel would reach for a given workgroup size, VGPR/SGPR usage and LDS. It
reuses the backend's own occupancy math, so the numbers match the compiler.
Includes a lit test and a CommandGuide man page.

Assisted-by: Claude Opus
---
 llvm/docs/CommandGuide/index.md               |   2 +
 .../docs/CommandGuide/llvm-calc-occupancy.rst | 107 +++++
 llvm/test/lit.cfg.py                          |   1 +
 .../test/tools/llvm-calc-occupancy/basic.test |  58 +++
 llvm/tools/llvm-calc-occupancy/CMakeLists.txt |  30 ++
 .../llvm-calc-occupancy.cpp                   | 410 ++++++++++++++++++
 6 files changed, 608 insertions(+)
 create mode 100644 llvm/docs/CommandGuide/llvm-calc-occupancy.rst
 create mode 100644 llvm/test/tools/llvm-calc-occupancy/basic.test
 create mode 100644 llvm/tools/llvm-calc-occupancy/CMakeLists.txt
 create mode 100644 llvm/tools/llvm-calc-occupancy/llvm-calc-occupancy.cpp

diff --git a/llvm/docs/CommandGuide/index.md b/llvm/docs/CommandGuide/index.md
index ac90d9468b848..b9c14f30c3010 100644
--- a/llvm/docs/CommandGuide/index.md
+++ b/llvm/docs/CommandGuide/index.md
@@ -15,6 +15,7 @@ llvm-addr2line
 llvm-ar
 llvm-as
 llvm-bcanalyzer
+llvm-calc-occupancy
 llvm-cgdata
 llvm-config
 llvm-cov
@@ -143,6 +144,7 @@ interested in.
 * {doc}`llvm-profgen`
 * {doc}`llvm-tli-checker`
 * {doc}`llvm-offload-binary`
+* {doc}`llvm-calc-occupancy`
 
 ## Remarks Tools
 
diff --git a/llvm/docs/CommandGuide/llvm-calc-occupancy.rst b/llvm/docs/CommandGuide/llvm-calc-occupancy.rst
new file mode 100644
index 0000000000000..61cebf52b3223
--- /dev/null
+++ b/llvm/docs/CommandGuide/llvm-calc-occupancy.rst
@@ -0,0 +1,107 @@
+llvm-calc-occupancy - AMDGPU occupancy calculator
+==================================================
+
+.. program:: llvm-calc-occupancy
+
+SYNOPSIS
+--------
+
+:program:`llvm-calc-occupancy` -mcpu=<gfxNNN> [*options*]
+
+DESCRIPTION
+-----------
+
+:program:`llvm-calc-occupancy` reports the occupancy (waves per execution unit)
+that an AMDGPU kernel would achieve for a given combination of workgroup size,
+VGPR usage, SGPR usage and LDS allocation. It is a thin front-end over the same
+occupancy math the AMDGPU backend uses (``GCNSubtarget``), so the numbers match
+what the compiler computes for an equivalent kernel.
+
+Any resource that is not specified is treated as unconstrained. When the
+workgroup size is left unspecified (or given as a range), the occupancy is
+reported as a range as well.
+
+The tool only supports the ``amdgcn`` target.
+
+EXAMPLE
+-------
+
+.. code-block:: console
+
+  $ llvm-calc-occupancy -mcpu=gfx90a --wg-size=512 --vgprs=50 --sgprs=30
+  llvm-calc-occupancy - AMDGPU occupancy calculator
+
+  Target
+    Triple:              amdgcn-amd-amdhsa
+    GPU (-mcpu):         gfx90a
+    Wavefront size:      64
+    Max waves/EU:        8 (waves per SIMD, hardware limit)
+    EUs (SIMDs) per CU:  4
+    ...
+
+  Per-constraint occupancy (waves/EU)
+    Workgroup + LDS:     8
+    VGPRs:               8
+    SGPRs:               8
+
+  Result
+    Occupancy:           8 waves/EU (32 waves/CU)
+    Limited by:          workgroup size / LDS, VGPRs, SGPRs
+    Next step:           already at the hardware maximum
+
+OPTIONS
+-------
+
+.. option:: -mcpu=<gfxNNN>
+
+  Target GPU, for example ``gfx90a``. Required.
+
+.. option:: -mtriple=<triple>
+
+  Target triple. Defaults to ``amdgcn-amd-amdhsa``. Must be an ``amdgcn``
+  triple.
+
+.. option:: -mattr=<features>
+
+  Comma-separated list of subtarget features, for example
+  ``+wavefrontsize32``.
+
+.. option:: --wg-size=<N>, --flat-workgroup-size=<N>
+
+  Flat workgroup size. Accepts a single value ``N`` or a range ``MIN:MAX``.
+  When omitted, the full legal range is assumed and the occupancy is reported
+  as a range.
+
+.. option:: --vgprs=<N>
+
+  Number of VGPRs used per lane. When omitted, VGPRs do not constrain the
+  occupancy.
+
+.. option:: --sgprs=<N>
+
+  Number of SGPRs used per wave. When omitted, SGPRs do not constrain the
+  occupancy.
+
+.. option:: --lds=<size>
+
+  LDS bytes allocated per workgroup. Accepts an optional ``k``/``kb`` or
+  ``m``/``mb`` suffix (base 1024).
+
+.. option:: --dynamic-vgpr-block-size=<N>
+
+  Dynamic VGPR block size. ``0`` (the default) disables dynamic VGPR mode.
+
+.. option:: --limits
+
+  Print, for each occupancy level supported by the GPU, the maximum number of
+  VGPRs and SGPRs that still reaches that level.
+
+.. option:: --help
+
+  Print a summary of command line options and exit.
+
+EXIT STATUS
+-----------
+
+:program:`llvm-calc-occupancy` returns 0 on success and a non-zero exit code if
+the arguments are invalid (for example a missing or non-``amdgcn`` target).
diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py
index cd028963dd59e..4f6f33c544ba7 100644
--- a/llvm/test/lit.cfg.py
+++ b/llvm/test/lit.cfg.py
@@ -347,6 +347,7 @@ def get_asan_rtlib():
         ToolSubst("OrcV2CBindingsLazy", unresolved="ignore"),
         ToolSubst("OrcV2CBindingsVeryLazy", unresolved="ignore"),
         ToolSubst("dxil-dis", unresolved="ignore"),
+        ToolSubst("llvm-calc-occupancy", unresolved="ignore"),
     ]
 )
 
diff --git a/llvm/test/tools/llvm-calc-occupancy/basic.test b/llvm/test/tools/llvm-calc-occupancy/basic.test
new file mode 100644
index 0000000000000..9c1656070973d
--- /dev/null
+++ b/llvm/test/tools/llvm-calc-occupancy/basic.test
@@ -0,0 +1,58 @@
+## Basic checks for the AMDGPU occupancy calculator.
+REQUIRES: amdgpu-registered-target
+
+## VGPR-limited kernel on gfx1030 (wave32, 16 waves/EU HW limit).
+RUN: llvm-calc-occupancy -mcpu=gfx1030 --vgprs=128 | FileCheck %s --check-prefix=VGPR
+VGPR: GPU (-mcpu):         gfx1030
+VGPR: Max waves/EU:        16
+VGPR: VGPRs:               8
+VGPR: Occupancy:           8 waves/EU (32 waves/CU)
+VGPR: Limited by:          VGPRs
+
+## VGPR-limited kernel reports how far VGPRs must drop for the next level.
+RUN: llvm-calc-occupancy -mcpu=gfx950 --vgprs=250 --wg-size=64 | FileCheck %s --check-prefix=NEXT
+NEXT: Limited by:          VGPRs
+NEXT: Next step:           reach 3 waves/EU:
+NEXT: VGPRs <= 168 (currently 250)
+
+## When several factors tie, all of them must be relaxed.
+RUN: llvm-calc-occupancy -mcpu=gfx90a --vgprs=80 --sgprs=102 | FileCheck %s --check-prefix=BOTH
+BOTH: Limited by:          VGPRs, SGPRs
+BOTH: Next step:           reach 7 waves/EU (requires all of):
+BOTH: VGPRs <= 72 (currently 80)
+BOTH: SGPRs <= 96 (currently 102)
+
+## An LDS-limited kernel is told how much LDS to shed.
+RUN: llvm-calc-occupancy -mcpu=gfx90a --wg-size=256 --lds=40k | FileCheck %s --check-prefix=NEXTLDS
+NEXTLDS: Limited by:          workgroup size / LDS
+NEXTLDS: Next step:           reach 2 waves/EU:
+NEXTLDS: LDS <= 32768 bytes (currently 40960)
+
+## No hint when already at the hardware maximum.
+RUN: llvm-calc-occupancy -mcpu=gfx90a --vgprs=40 --sgprs=20 --wg-size=64 | FileCheck %s --check-prefix=MAXED
+MAXED: Occupancy:           8 waves/EU
+MAXED: Next step:           already at the hardware maximum
+
+## No workgroup size given: the workgroup dimension is reported as a range.
+RUN: llvm-calc-occupancy -mcpu=gfx90a | FileCheck %s --check-prefix=FUZZY
+FUZZY: Workgroup size:      1 .. 1024 (unspecified -> full range)
+FUZZY: Occupancy:           8 waves/EU (32 waves/CU)
+
+## Requesting more LDS than the GPU has warns and pins occupancy to 1.
+RUN: llvm-calc-occupancy -mcpu=gfx90a --lds=103kb 2>&1 | FileCheck %s --check-prefix=BIGLDS
+BIGLDS: warning:{{.*}}LDS request (105472 bytes) exceeds addressable LDS
+BIGLDS: Occupancy:           1 waves/EU
+
+## The register-limit table can be dumped for a GPU.
+RUN: llvm-calc-occupancy -mcpu=gfx90a --limits | FileCheck %s --check-prefix=LIMITS
+LIMITS: Occupancy      Max VGPRs      Max SGPRs
+LIMITS: 8              64             80
+LIMITS: 1              512            102
+
+## Missing -mcpu is an error.
+RUN: not llvm-calc-occupancy 2>&1 | FileCheck %s --check-prefix=NOCPU
+NOCPU: error:{{.*}}no GPU specified
+
+## A non-AMDGPU triple is rejected.
+RUN: not llvm-calc-occupancy -mtriple=x86_64-- -mcpu=gfx90a 2>&1 | FileCheck %s --check-prefix=BADTRIPLE
+BADTRIPLE: error:{{.*}}only supports the amdgcn target
diff --git a/llvm/tools/llvm-calc-occupancy/CMakeLists.txt b/llvm/tools/llvm-calc-occupancy/CMakeLists.txt
new file mode 100644
index 0000000000000..9580d855f06a3
--- /dev/null
+++ b/llvm/tools/llvm-calc-occupancy/CMakeLists.txt
@@ -0,0 +1,30 @@
+# This tool depends on AMDGPU backend internals (GCNSubtarget). Only build it
+# when the AMDGPU target is enabled.
+if(NOT "AMDGPU" IN_LIST LLVM_TARGETS_TO_BUILD)
+  return()
+endif()
+
+include_directories(
+  ${LLVM_MAIN_SRC_DIR}/lib/Target/AMDGPU
+  ${LLVM_BINARY_DIR}/lib/Target/AMDGPU
+  )
+
+set(LLVM_LINK_COMPONENTS
+  AMDGPUCodeGen
+  AMDGPUDesc
+  AMDGPUInfo
+  AMDGPUUtils
+  CodeGen
+  Core
+  MC
+  Support
+  Target
+  TargetParser
+  )
+
+add_llvm_tool(llvm-calc-occupancy
+  llvm-calc-occupancy.cpp
+
+  DEPENDS
+  AMDGPUCommonTableGen
+  )
diff --git a/llvm/tools/llvm-calc-occupancy/llvm-calc-occupancy.cpp b/llvm/tools/llvm-calc-occupancy/llvm-calc-occupancy.cpp
new file mode 100644
index 0000000000000..422161e7fa7ea
--- /dev/null
+++ b/llvm/tools/llvm-calc-occupancy/llvm-calc-occupancy.cpp
@@ -0,0 +1,410 @@
+//===-- llvm-calc-occupancy.cpp - AMDGPU occupancy calculator -------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// A small standalone utility that answers "what occupancy do I get?" for an
+// AMDGPU kernel, given some subset of its resource usage: workgroup size,
+// VGPRs, SGPRs and LDS. Fields that are left unspecified are treated as
+// unconstrained, and the result is reported as a range (waves per EU).
+//
+// It reuses the compiler's own occupancy math (GCNSubtarget) so the numbers
+// match what the backend would compute for the same inputs.
+//
+// Example:
+//   llvm-calc-occupancy -mcpu=gfx90a --wg-size=512 --vgprs=50 --sgprs=30 \
+//                       --lds=103kb
+//
+//===----------------------------------------------------------------------===//
+
+#include "AMDGPUTargetMachine.h"
+#include "GCNSubtarget.h"
+#include "Utils/AMDGPUBaseInfo.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/TargetSelect.h"
+#include "llvm/Support/WithColor.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Target/TargetMachine.h"
+#include "llvm/TargetParser/Triple.h"
+#include <optional>
+
+using namespace llvm;
+
+namespace {
+cl::OptionCategory OccCategory("llvm-calc-occupancy options");
+
+cl::opt<std::string> TripleName("mtriple", cl::desc("Target triple"),
+                                cl::init("amdgcn-amd-amdhsa"),
+                                cl::cat(OccCategory));
+
+cl::opt<std::string> MCPU("mcpu", cl::desc("Target GPU (e.g. gfx90a)"),
+                          cl::init(""), cl::cat(OccCategory));
+
+cl::opt<std::string> MAttr("mattr",
+                           cl::desc("Comma-separated subtarget features "
+                                    "(e.g. +wavefrontsize32)"),
+                           cl::init(""), cl::cat(OccCategory));
+
+cl::opt<std::string>
+    WGSizeStr("wg-size",
+              cl::desc("Flat workgroup size: single value 'N' or range "
+                       "'MIN:MAX' (default: 1:1024)"),
+              cl::init(""), cl::cat(OccCategory));
+cl::alias WGSizeAlias("flat-workgroup-size", cl::aliasopt(WGSizeStr));
+
+cl::opt<int> NumVGPRs("vgprs", cl::desc("VGPRs used per lane (default: none)"),
+                      cl::init(-1), cl::cat(OccCategory));
+
+cl::opt<int> NumSGPRs("sgprs", cl::desc("SGPRs used per wave (default: none)"),
+                      cl::init(-1), cl::cat(OccCategory));
+
+cl::opt<std::string> LDSStr("lds",
+                            cl::desc("LDS bytes per workgroup, accepts k/kb/m "
+                                     "suffixes (default: 0)"),
+                            cl::init(""), cl::cat(OccCategory));
+
+cl::opt<unsigned>
+    DynVGPRBlockSize("dynamic-vgpr-block-size",
+                     cl::desc("Dynamic VGPR block size (0 = disabled)"),
+                     cl::init(0), cl::cat(OccCategory));
+
+cl::opt<bool> ShowLimits("limits",
+                         cl::desc("Print the per-occupancy VGPR/SGPR limit "
+                                  "table for this GPU"),
+                         cl::init(false), cl::cat(OccCategory));
+} // namespace
+
+// Parse a byte size with an optional binary suffix (k/kb/kib/m/mb/mib), all
+// base 1024. A bare number is interpreted as bytes.
+static bool parseSize(StringRef S, uint64_t &Out) {
+  S = S.trim();
+  if (S.empty())
+    return false;
+  uint64_t Mult = 1;
+  StringRef Lower = S.lower();
+  static const std::pair<StringRef, uint64_t> Suffixes[] = {
+      {"kib", 1024},        {"kb", 1024},        {"k", 1024},
+      {"mib", 1024 * 1024}, {"mb", 1024 * 1024}, {"m", 1024 * 1024}};
+  for (const auto &[Suf, M] : Suffixes) {
+    if (Lower.ends_with(Suf)) {
+      Mult = M;
+      S = S.drop_back(Suf.size()).rtrim();
+      break;
+    }
+  }
+  uint64_t Value;
+  if (S.getAsInteger(10, Value))
+    return false;
+  Out = Value * Mult;
+  return true;
+}
+
+// Parse "N" or "MIN:MAX" (also accepts "MIN-MAX") into a flat workgroup range.
+static bool parseWGRange(StringRef S, unsigned &Min, unsigned &Max) {
+  S = S.trim();
+  StringRef LHS, RHS;
+  if (S.contains(':'))
+    std::tie(LHS, RHS) = S.split(':');
+  else if (S.contains('-'))
+    std::tie(LHS, RHS) = S.split('-');
+  else
+    LHS = RHS = S;
+
+  unsigned Lo, Hi;
+  if (LHS.trim().getAsInteger(10, Lo) || RHS.trim().getAsInteger(10, Hi))
+    return false;
+  if (Lo == 0 || Hi == 0 || Lo > Hi)
+    return false;
+  Min = Lo;
+  Max = Hi;
+  return true;
+}
+
+static std::string formatBytes(uint64_t Bytes) {
+  if (Bytes && Bytes % 1024 == 0)
+    return (Twine(Bytes) + " bytes (" + Twine(Bytes / 1024) + " KiB)").str();
+  return (Twine(Bytes) + " bytes").str();
+}
+
+int main(int argc, char **argv) {
+  InitLLVM X(argc, argv);
+
+  cl::HideUnrelatedOptions(OccCategory);
+  cl::ParseCommandLineOptions(
+      argc, argv,
+      "AMDGPU occupancy calculator\n\n"
+      "  Prints the occupancy (waves per EU) implied by a given workgroup "
+      "size,\n"
+      "  VGPR/SGPR usage and LDS allocation. Unspecified fields are reported "
+      "as\n"
+      "  a range.\n");
+
+  LLVMInitializeAMDGPUTargetInfo();
+  LLVMInitializeAMDGPUTarget();
+  LLVMInitializeAMDGPUTargetMC();
+
+  if (MCPU.empty()) {
+    WithColor::error(errs(), "llvm-calc-occupancy")
+        << "no GPU specified; pass -mcpu=<gfxNNN> (e.g. -mcpu=gfx90a)\n";
+    return 1;
+  }
+
+  Triple TT(Triple::normalize(TripleName));
+  if (!TT.isAMDGCN()) {
+    WithColor::error(errs(), "llvm-calc-occupancy")
+        << "this tool only supports the amdgcn target; got triple '" << TT.str()
+        << "'\n";
+    return 1;
+  }
+
+  std::string Error;
+  const Target *T = TargetRegistry::lookupTarget(TT, Error);
+  if (!T) {
+    WithColor::error(errs(), "llvm-calc-occupancy") << Error << "\n";
+    return 1;
+  }
+
+  TargetOptions Options;
+  std::unique_ptr<TargetMachine> TM(T->createTargetMachine(
+      TT, MCPU, MAttr, Options, std::nullopt, std::nullopt));
+  if (!TM) {
+    WithColor::error(errs(), "llvm-calc-occupancy")
+        << "failed to create target machine for '" << MCPU << "'\n";
+    return 1;
+  }
+
+  GCNSubtarget ST(TM->getTargetTriple(), std::string(TM->getTargetCPU()),
+                  std::string(TM->getTargetFeatureString()),
+                  *static_cast<GCNTargetMachine *>(TM.get()));
+
+  const MCSubtargetInfo &STI = ST;
+
+  // Parse inputs.
+  unsigned WGMin = 1, WGMax = AMDGPU::IsaInfo::getMaxFlatWorkGroupSize();
+  bool WGSpecified = !WGSizeStr.empty();
+  if (WGSpecified && !parseWGRange(WGSizeStr, WGMin, WGMax)) {
+    WithColor::error(errs(), "llvm-calc-occupancy")
+        << "invalid --wg-size '" << WGSizeStr << "'\n";
+    return 1;
+  }
+
+  uint64_t LDSBytes = 0;
+  bool LDSSpecified = !LDSStr.empty();
+  if (LDSSpecified && !parseSize(LDSStr, LDSBytes)) {
+    WithColor::error(errs(), "llvm-calc-occupancy")
+        << "invalid --lds '" << LDSStr << "'\n";
+    return 1;
+  }
+
+  bool VGPRSpecified = NumVGPRs >= 0;
+  bool SGPRSpecified = NumSGPRs >= 0;
+
+  // Hardware characteristics.
+  unsigned WaveSize = AMDGPU::IsaInfo::getWavefrontSize(STI);
+  unsigned MaxWaves = AMDGPU::IsaInfo::getMaxWavesPerEU(STI);
+  unsigned EUsPerCU = AMDGPU::IsaInfo::getEUsPerCU(STI);
+  unsigned LocalMemSize = AMDGPU::IsaInfo::getLocalMemorySize(STI);
+  unsigned AddrLocalMem = AMDGPU::IsaInfo::getAddressableLocalMemorySize(STI);
+  unsigned AddrVGPRs =
+      AMDGPU::IsaInfo::getAddressableNumVGPRs(STI, DynVGPRBlockSize);
+  unsigned AddrSGPRs = AMDGPU::IsaInfo::getAddressableNumSGPRs(STI);
+  unsigned MaxWGSize = AMDGPU::IsaInfo::getMaxFlatWorkGroupSize();
+
+  // Warn about inputs that exceed the hardware's physical capacity: such a
+  // kernel could not actually launch, so the reported occupancy is only the
+  // math extrapolated past the limit.
+  auto Warn = [](const Twine &Msg) {
+    WithColor::warning(errs(), "llvm-calc-occupancy") << Msg << "\n";
+  };
+  if (LDSSpecified && LDSBytes > AddrLocalMem)
+    Warn("LDS request (" + Twine(LDSBytes) +
+         " bytes) exceeds addressable LDS "
+         "per workgroup (" +
+         Twine(AddrLocalMem) + " bytes)");
+  if (VGPRSpecified && static_cast<unsigned>(NumVGPRs) > AddrVGPRs)
+    Warn("VGPR request (" + Twine(static_cast<int>(NumVGPRs)) +
+         ") exceeds addressable "
+         "VGPRs (" +
+         Twine(AddrVGPRs) + ")");
+  if (SGPRSpecified && static_cast<unsigned>(NumSGPRs) > AddrSGPRs)
+    Warn("SGPR request (" + Twine(static_cast<int>(NumSGPRs)) +
+         ") exceeds addressable "
+         "SGPRs (" +
+         Twine(AddrSGPRs) + ")");
+  if (WGMax > MaxWGSize)
+    Warn("workgroup size (" + Twine(WGMax) +
+         ") exceeds the maximum flat "
+         "workgroup size (" +
+         Twine(MaxWGSize) + ")");
+
+  outs() << "llvm-calc-occupancy - AMDGPU occupancy calculator\n\n";
+  outs() << "Target\n";
+  outs() << format("  %-20s %s\n", "Triple:", TT.str().c_str());
+  outs() << format("  %-20s %s\n",
+                   "GPU (-mcpu):", std::string(TM->getTargetCPU()).c_str());
+  outs() << format("  %-20s %u\n", "Wavefront size:", WaveSize);
+  outs() << format("  %-20s %u (waves per SIMD, hardware limit)\n",
+                   "Max waves/EU:", MaxWaves);
+  outs() << format("  %-20s %u\n", "EUs (SIMDs) per CU:", EUsPerCU);
+  outs() << format("  %-20s %s\n",
+                   "LDS per CU:", formatBytes(LocalMemSize).c_str());
+  outs() << format("  %-20s %s\n", "Addressable LDS:",
+                   (formatBytes(AddrLocalMem) + " per workgroup").c_str());
+
+  outs() << "\nInputs\n";
+  if (WGSpecified) {
+    if (WGMin == WGMax)
+      outs() << format("  %-20s %u\n", "Workgroup size:", WGMin);
+    else
+      outs() << format("  %-20s %u .. %u\n", "Workgroup size:", WGMin, WGMax);
+  } else {
+    outs() << format("  %-20s %u .. %u (unspecified -> full range)\n",
+                     "Workgroup size:", WGMin, WGMax);
+  }
+  if (VGPRSpecified)
+    outs() << format("  %-20s %d\n", "VGPRs per lane:", (int)NumVGPRs);
+  else
+    outs() << format("  %-20s %s\n", "VGPRs per lane:", "unspecified");
+  if (SGPRSpecified)
+    outs() << format("  %-20s %d\n", "SGPRs per wave:", (int)NumSGPRs);
+  else
+    outs() << format("  %-20s %s\n", "SGPRs per wave:", "unspecified");
+  if (LDSSpecified)
+    outs() << format("  %-20s %s\n",
+                     "LDS per workgroup:", formatBytes(LDSBytes).c_str());
+  else
+    outs() << format("  %-20s %s\n", "LDS per workgroup:", "unspecified (0)");
+
+  // Per-constraint occupancy (all in waves/EU).
+  std::pair<unsigned, unsigned> WGOcc = ST.getOccupancyWithWorkGroupSizes(
+      static_cast<uint32_t>(LDSBytes), {WGMin, WGMax});
+  unsigned VGPROcc =
+      VGPRSpecified ? ST.getOccupancyWithNumVGPRs(NumVGPRs, DynVGPRBlockSize)
+                    : MaxWaves;
+  unsigned SGPROcc =
+      SGPRSpecified ? ST.getOccupancyWithNumSGPRs(NumSGPRs) : MaxWaves;
+
+  outs() << "\nPer-constraint occupancy (waves/EU)\n";
+  if (WGOcc.first == WGOcc.second)
+    outs() << format("  %-20s %u\n", "Workgroup + LDS:", WGOcc.second);
+  else
+    outs() << format("  %-20s %u .. %u\n", "Workgroup + LDS:", WGOcc.first,
+                     WGOcc.second);
+  if (VGPRSpecified)
+    outs() << format("  %-20s %u\n", "VGPRs:", VGPROcc);
+  if (SGPRSpecified)
+    outs() << format("  %-20s %u\n", "SGPRs:", SGPROcc);
+
+  // Combine like GCNSubtarget::computeOccupancy.
+  unsigned MaxOcc = std::min({WGOcc.second, VGPROcc, SGPROcc});
+  unsigned MinOcc = std::min(WGOcc.first, MaxOcc);
+
+  // Identify what pins the maximum occupancy.
+  SmallVector<StringRef, 3> LimitedBy;
+  if (WGOcc.second == MaxOcc)
+    LimitedBy.push_back("workgroup size / LDS");
+  if (VGPRSpecified && VGPROcc == MaxOcc)
+    LimitedBy.push_back("VGPRs");
+  if (SGPRSpecified && SGPROcc == MaxOcc)
+    LimitedBy.push_back("SGPRs");
+
+  outs() << "\nResult\n";
+  if (MinOcc == MaxOcc)
+    outs() << format("  %-20s %u waves/EU (%u waves/CU)\n",
+                     "Occupancy:", MaxOcc, MaxOcc * EUsPerCU);
+  else
+    outs() << format("  %-20s %u .. %u waves/EU (%u .. %u waves/CU)\n",
+                     "Occupancy:", MinOcc, MaxOcc, MinOcc * EUsPerCU,
+                     MaxOcc * EUsPerCU);
+  outs() << format("  %-20s %s\n",
+                   "Limited by:", join(LimitedBy, ", ").c_str());
+
+  // Hint: what would it take to gain one more wave/EU. Every factor that
+  // currently pins the occupancy has to be relaxed, so list each one.
+  if (MaxOcc >= MaxWaves) {
+    outs() << format("  %-20s already at the hardware maximum\n", "Next step:");
+  } else {
+    unsigned TargetOcc = MaxOcc + 1;
+    outs() << format("  %-20s reach %u waves/EU%s\n", "Next step:", TargetOcc,
+                     LimitedBy.size() > 1 ? " (requires all of):" : ":");
+
+    if (VGPRSpecified && VGPROcc == MaxOcc) {
+      unsigned MaxV =
+          AMDGPU::IsaInfo::getMaxNumVGPRs(STI, TargetOcc, DynVGPRBlockSize);
+      outs() << format("      VGPRs <= %u (currently %d)\n", MaxV,
+                       static_cast<int>(NumVGPRs));
+    }
+    if (SGPRSpecified && SGPROcc == MaxOcc) {
+      unsigned MaxS =
+          AMDGPU::IsaInfo::getMaxNumSGPRs(STI, TargetOcc, /*Addressable=*/true);
+      outs() << format("      SGPRs <= %u (currently %d)\n", MaxS,
+                       static_cast<int>(NumSGPRs));
+    }
+    if (WGOcc.second == MaxOcc) {
+      auto WGLDSOcc = [&](uint32_t LDS, unsigned Lo, unsigned Hi) {
+        return ST.getOccupancyWithWorkGroupSizes(LDS, {Lo, Hi}).second;
+      };
+      uint32_t LDS32 = static_cast<uint32_t>(LDSBytes);
+      bool Suggested = false;
+      // LDS lever: largest LDS that still reaches TargetOcc at current WG.
+      if (LDSSpecified && LDSBytes > 0 &&
+          WGLDSOcc(0, WGMin, WGMax) >= TargetOcc) {
+        uint64_t Lo = 0, Hi = LDSBytes;
+        while (Lo < Hi) {
+          uint64_t Mid = Lo + (Hi - Lo + 1) / 2;
+          if (WGLDSOcc(static_cast<uint32_t>(Mid), WGMin, WGMax) >= TargetOcc)
+            Lo = Mid;
+          else
+            Hi = Mid - 1;
+        }
+        outs() << format("      LDS <= %llu bytes (currently %llu)\n",
+                         static_cast<unsigned long long>(Lo),
+                         static_cast<unsigned long long>(LDSBytes));
+        Suggested = true;
+      }
+      // Workgroup lever: largest flat workgroup size that reaches TargetOcc.
+      if (WGMax > 1 && WGLDSOcc(LDS32, 1, 1) >= TargetOcc) {
+        unsigned Lo = 1, Hi = WGMax;
+        while (Lo < Hi) {
+          unsigned Mid = Lo + (Hi - Lo + 1) / 2;
+          if (WGLDSOcc(LDS32, Mid, Mid) >= TargetOcc)
+            Lo = Mid;
+          else
+            Hi = Mid - 1;
+        }
+        if (Lo < WGMax) {
+          outs() << format("      workgroup size <= %u (currently %u)\n", Lo,
+                           WGMax);
+          Suggested = true;
+        }
+      }
+      if (!Suggested)
+        outs() << "      reduce workgroup size and/or LDS\n";
+    }
+  }
+
+  if (ShowLimits) {
+    outs() << "\nPer-occupancy register limits (max regs to still reach "
+              "each level)\n";
+    outs() << format("  %-14s %-14s %-14s\n", "Occupancy", "Max VGPRs",
+                     "Max SGPRs");
+    for (unsigned Occ = MaxWaves; Occ >= 1; --Occ) {
+      unsigned MaxV =
+          AMDGPU::IsaInfo::getMaxNumVGPRs(STI, Occ, DynVGPRBlockSize);
+      unsigned MaxS =
+          AMDGPU::IsaInfo::getMaxNumSGPRs(STI, Occ, /*Addressable=*/true);
+      outs() << format("  %-14u %-14u %-14u\n", Occ, MaxV, MaxS);
+    }
+  }
+
+  return 0;
+}

>From e9e0468a3b64847502cacd9f03812c6037a1b5e7 Mon Sep 17 00:00:00 2001
From: mselehov <mselehov at amd.com>
Date: Fri, 10 Jul 2026 10:03:22 -0500
Subject: [PATCH 2/3] [llvm-calc-occupancy] Address review feedback

- Fix dynamic VGPR block size: fall back to the subtarget default when 0
  and dynamic VGPRs are enabled, matching GCNSubtarget::computeOccupancy.
- Default to the amdgpu triple and advise it in the docs (legacy amdgcn
  still accepted).
- Add a TODO to drop the codegen dependency once the occupancy math is
  available through TargetParser.
- Use a structured binding for the workgroup occupancy range.
- Use argv[0] instead of a hardcoded name in diagnostics.

Assisted-by: Cursor
---
 .../docs/CommandGuide/llvm-calc-occupancy.rst | 12 ++--
 .../test/tools/llvm-calc-occupancy/basic.test |  2 +-
 .../llvm-calc-occupancy.cpp                   | 65 +++++++++++--------
 3 files changed, 47 insertions(+), 32 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-calc-occupancy.rst b/llvm/docs/CommandGuide/llvm-calc-occupancy.rst
index 61cebf52b3223..72f972ccae097 100644
--- a/llvm/docs/CommandGuide/llvm-calc-occupancy.rst
+++ b/llvm/docs/CommandGuide/llvm-calc-occupancy.rst
@@ -21,7 +21,9 @@ Any resource that is not specified is treated as unconstrained. When the
 workgroup size is left unspecified (or given as a range), the occupancy is
 reported as a range as well.
 
-The tool only supports the ``amdgcn`` target.
+The tool only supports the AMDGPU target. New callers should use the
+``amdgpu`` triple (for example ``amdgpu-amd-amdhsa``); the legacy ``amdgcn``
+spelling is still accepted.
 
 EXAMPLE
 -------
@@ -32,7 +34,7 @@ EXAMPLE
   llvm-calc-occupancy - AMDGPU occupancy calculator
 
   Target
-    Triple:              amdgcn-amd-amdhsa
+    Triple:              amdgpu-amd-amdhsa
     GPU (-mcpu):         gfx90a
     Wavefront size:      64
     Max waves/EU:        8 (waves per SIMD, hardware limit)
@@ -58,8 +60,8 @@ OPTIONS
 
 .. option:: -mtriple=<triple>
 
-  Target triple. Defaults to ``amdgcn-amd-amdhsa``. Must be an ``amdgcn``
-  triple.
+  Target triple. Defaults to ``amdgpu-amd-amdhsa``. Must be an AMDGPU triple
+  (the ``amdgpu`` or legacy ``amdgcn`` arch).
 
 .. option:: -mattr=<features>
 
@@ -104,4 +106,4 @@ EXIT STATUS
 -----------
 
 :program:`llvm-calc-occupancy` returns 0 on success and a non-zero exit code if
-the arguments are invalid (for example a missing or non-``amdgcn`` target).
+the arguments are invalid (for example a missing or non-AMDGPU target).
diff --git a/llvm/test/tools/llvm-calc-occupancy/basic.test b/llvm/test/tools/llvm-calc-occupancy/basic.test
index 9c1656070973d..3c2f63229df59 100644
--- a/llvm/test/tools/llvm-calc-occupancy/basic.test
+++ b/llvm/test/tools/llvm-calc-occupancy/basic.test
@@ -55,4 +55,4 @@ NOCPU: error:{{.*}}no GPU specified
 
 ## A non-AMDGPU triple is rejected.
 RUN: not llvm-calc-occupancy -mtriple=x86_64-- -mcpu=gfx90a 2>&1 | FileCheck %s --check-prefix=BADTRIPLE
-BADTRIPLE: error:{{.*}}only supports the amdgcn target
+BADTRIPLE: error:{{.*}}only supports the AMDGPU target
diff --git a/llvm/tools/llvm-calc-occupancy/llvm-calc-occupancy.cpp b/llvm/tools/llvm-calc-occupancy/llvm-calc-occupancy.cpp
index 422161e7fa7ea..764089e63ad73 100644
--- a/llvm/tools/llvm-calc-occupancy/llvm-calc-occupancy.cpp
+++ b/llvm/tools/llvm-calc-occupancy/llvm-calc-occupancy.cpp
@@ -14,6 +14,11 @@
 // It reuses the compiler's own occupancy math (GCNSubtarget) so the numbers
 // match what the backend would compute for the same inputs.
 //
+// TODO: This links the AMDGPU codegen libraries only because the occupancy
+// math currently lives in GCNSubtarget. Once that subtarget information is
+// exposed through TargetParser, this tool should depend on TargetParser alone
+// and drop the codegen dependency.
+//
 // Example:
 //   llvm-calc-occupancy -mcpu=gfx90a --wg-size=512 --vgprs=50 --sgprs=30 \
 //                       --lds=103kb
@@ -43,7 +48,7 @@ namespace {
 cl::OptionCategory OccCategory("llvm-calc-occupancy options");
 
 cl::opt<std::string> TripleName("mtriple", cl::desc("Target triple"),
-                                cl::init("amdgcn-amd-amdhsa"),
+                                cl::init("amdgpu-amd-amdhsa"),
                                 cl::cat(OccCategory));
 
 cl::opt<std::string> MCPU("mcpu", cl::desc("Target GPU (e.g. gfx90a)"),
@@ -137,6 +142,7 @@ static std::string formatBytes(uint64_t Bytes) {
 
 int main(int argc, char **argv) {
   InitLLVM X(argc, argv);
+  const char *ToolName = argv[0];
 
   cl::HideUnrelatedOptions(OccCategory);
   cl::ParseCommandLineOptions(
@@ -153,15 +159,15 @@ int main(int argc, char **argv) {
   LLVMInitializeAMDGPUTargetMC();
 
   if (MCPU.empty()) {
-    WithColor::error(errs(), "llvm-calc-occupancy")
+    WithColor::error(errs(), ToolName)
         << "no GPU specified; pass -mcpu=<gfxNNN> (e.g. -mcpu=gfx90a)\n";
     return 1;
   }
 
   Triple TT(Triple::normalize(TripleName));
   if (!TT.isAMDGCN()) {
-    WithColor::error(errs(), "llvm-calc-occupancy")
-        << "this tool only supports the amdgcn target; got triple '" << TT.str()
+    WithColor::error(errs(), ToolName)
+        << "this tool only supports the AMDGPU target; got triple '" << TT.str()
         << "'\n";
     return 1;
   }
@@ -169,7 +175,7 @@ int main(int argc, char **argv) {
   std::string Error;
   const Target *T = TargetRegistry::lookupTarget(TT, Error);
   if (!T) {
-    WithColor::error(errs(), "llvm-calc-occupancy") << Error << "\n";
+    WithColor::error(errs(), ToolName) << Error << "\n";
     return 1;
   }
 
@@ -177,7 +183,7 @@ int main(int argc, char **argv) {
   std::unique_ptr<TargetMachine> TM(T->createTargetMachine(
       TT, MCPU, MAttr, Options, std::nullopt, std::nullopt));
   if (!TM) {
-    WithColor::error(errs(), "llvm-calc-occupancy")
+    WithColor::error(errs(), ToolName)
         << "failed to create target machine for '" << MCPU << "'\n";
     return 1;
   }
@@ -188,11 +194,17 @@ int main(int argc, char **argv) {
 
   const MCSubtargetInfo &STI = ST;
 
+  // Mirror GCNSubtarget::computeOccupancy: when the block size is not given
+  // explicitly, fall back to the subtarget's default if dynamic VGPRs are on.
+  unsigned DynVGPRBlockSizeEff = DynVGPRBlockSize;
+  if (DynVGPRBlockSizeEff == 0 && ST.isDynamicVGPREnabled())
+    DynVGPRBlockSizeEff = ST.getDynamicVGPRBlockSize();
+
   // Parse inputs.
   unsigned WGMin = 1, WGMax = AMDGPU::IsaInfo::getMaxFlatWorkGroupSize();
   bool WGSpecified = !WGSizeStr.empty();
   if (WGSpecified && !parseWGRange(WGSizeStr, WGMin, WGMax)) {
-    WithColor::error(errs(), "llvm-calc-occupancy")
+    WithColor::error(errs(), ToolName)
         << "invalid --wg-size '" << WGSizeStr << "'\n";
     return 1;
   }
@@ -200,8 +212,7 @@ int main(int argc, char **argv) {
   uint64_t LDSBytes = 0;
   bool LDSSpecified = !LDSStr.empty();
   if (LDSSpecified && !parseSize(LDSStr, LDSBytes)) {
-    WithColor::error(errs(), "llvm-calc-occupancy")
-        << "invalid --lds '" << LDSStr << "'\n";
+    WithColor::error(errs(), ToolName) << "invalid --lds '" << LDSStr << "'\n";
     return 1;
   }
 
@@ -215,15 +226,15 @@ int main(int argc, char **argv) {
   unsigned LocalMemSize = AMDGPU::IsaInfo::getLocalMemorySize(STI);
   unsigned AddrLocalMem = AMDGPU::IsaInfo::getAddressableLocalMemorySize(STI);
   unsigned AddrVGPRs =
-      AMDGPU::IsaInfo::getAddressableNumVGPRs(STI, DynVGPRBlockSize);
+      AMDGPU::IsaInfo::getAddressableNumVGPRs(STI, DynVGPRBlockSizeEff);
   unsigned AddrSGPRs = AMDGPU::IsaInfo::getAddressableNumSGPRs(STI);
   unsigned MaxWGSize = AMDGPU::IsaInfo::getMaxFlatWorkGroupSize();
 
   // Warn about inputs that exceed the hardware's physical capacity: such a
   // kernel could not actually launch, so the reported occupancy is only the
   // math extrapolated past the limit.
-  auto Warn = [](const Twine &Msg) {
-    WithColor::warning(errs(), "llvm-calc-occupancy") << Msg << "\n";
+  auto Warn = [ToolName](const Twine &Msg) {
+    WithColor::warning(errs(), ToolName) << Msg << "\n";
   };
   if (LDSSpecified && LDSBytes > AddrLocalMem)
     Warn("LDS request (" + Twine(LDSBytes) +
@@ -271,11 +282,13 @@ int main(int argc, char **argv) {
                      "Workgroup size:", WGMin, WGMax);
   }
   if (VGPRSpecified)
-    outs() << format("  %-20s %d\n", "VGPRs per lane:", (int)NumVGPRs);
+    outs() << format("  %-20s %d\n",
+                     "VGPRs per lane:", static_cast<int>(NumVGPRs));
   else
     outs() << format("  %-20s %s\n", "VGPRs per lane:", "unspecified");
   if (SGPRSpecified)
-    outs() << format("  %-20s %d\n", "SGPRs per wave:", (int)NumSGPRs);
+    outs() << format("  %-20s %d\n",
+                     "SGPRs per wave:", static_cast<int>(NumSGPRs));
   else
     outs() << format("  %-20s %s\n", "SGPRs per wave:", "unspecified");
   if (LDSSpecified)
@@ -285,32 +298,32 @@ int main(int argc, char **argv) {
     outs() << format("  %-20s %s\n", "LDS per workgroup:", "unspecified (0)");
 
   // Per-constraint occupancy (all in waves/EU).
-  std::pair<unsigned, unsigned> WGOcc = ST.getOccupancyWithWorkGroupSizes(
+  auto [WGMinOcc, WGMaxOcc] = ST.getOccupancyWithWorkGroupSizes(
       static_cast<uint32_t>(LDSBytes), {WGMin, WGMax});
   unsigned VGPROcc =
-      VGPRSpecified ? ST.getOccupancyWithNumVGPRs(NumVGPRs, DynVGPRBlockSize)
+      VGPRSpecified ? ST.getOccupancyWithNumVGPRs(NumVGPRs, DynVGPRBlockSizeEff)
                     : MaxWaves;
   unsigned SGPROcc =
       SGPRSpecified ? ST.getOccupancyWithNumSGPRs(NumSGPRs) : MaxWaves;
 
   outs() << "\nPer-constraint occupancy (waves/EU)\n";
-  if (WGOcc.first == WGOcc.second)
-    outs() << format("  %-20s %u\n", "Workgroup + LDS:", WGOcc.second);
+  if (WGMinOcc == WGMaxOcc)
+    outs() << format("  %-20s %u\n", "Workgroup + LDS:", WGMaxOcc);
   else
-    outs() << format("  %-20s %u .. %u\n", "Workgroup + LDS:", WGOcc.first,
-                     WGOcc.second);
+    outs() << format("  %-20s %u .. %u\n", "Workgroup + LDS:", WGMinOcc,
+                     WGMaxOcc);
   if (VGPRSpecified)
     outs() << format("  %-20s %u\n", "VGPRs:", VGPROcc);
   if (SGPRSpecified)
     outs() << format("  %-20s %u\n", "SGPRs:", SGPROcc);
 
   // Combine like GCNSubtarget::computeOccupancy.
-  unsigned MaxOcc = std::min({WGOcc.second, VGPROcc, SGPROcc});
-  unsigned MinOcc = std::min(WGOcc.first, MaxOcc);
+  unsigned MaxOcc = std::min({WGMaxOcc, VGPROcc, SGPROcc});
+  unsigned MinOcc = std::min(WGMinOcc, MaxOcc);
 
   // Identify what pins the maximum occupancy.
   SmallVector<StringRef, 3> LimitedBy;
-  if (WGOcc.second == MaxOcc)
+  if (WGMaxOcc == MaxOcc)
     LimitedBy.push_back("workgroup size / LDS");
   if (VGPRSpecified && VGPROcc == MaxOcc)
     LimitedBy.push_back("VGPRs");
@@ -339,7 +352,7 @@ int main(int argc, char **argv) {
 
     if (VGPRSpecified && VGPROcc == MaxOcc) {
       unsigned MaxV =
-          AMDGPU::IsaInfo::getMaxNumVGPRs(STI, TargetOcc, DynVGPRBlockSize);
+          AMDGPU::IsaInfo::getMaxNumVGPRs(STI, TargetOcc, DynVGPRBlockSizeEff);
       outs() << format("      VGPRs <= %u (currently %d)\n", MaxV,
                        static_cast<int>(NumVGPRs));
     }
@@ -349,7 +362,7 @@ int main(int argc, char **argv) {
       outs() << format("      SGPRs <= %u (currently %d)\n", MaxS,
                        static_cast<int>(NumSGPRs));
     }
-    if (WGOcc.second == MaxOcc) {
+    if (WGMaxOcc == MaxOcc) {
       auto WGLDSOcc = [&](uint32_t LDS, unsigned Lo, unsigned Hi) {
         return ST.getOccupancyWithWorkGroupSizes(LDS, {Lo, Hi}).second;
       };
@@ -399,7 +412,7 @@ int main(int argc, char **argv) {
                      "Max SGPRs");
     for (unsigned Occ = MaxWaves; Occ >= 1; --Occ) {
       unsigned MaxV =
-          AMDGPU::IsaInfo::getMaxNumVGPRs(STI, Occ, DynVGPRBlockSize);
+          AMDGPU::IsaInfo::getMaxNumVGPRs(STI, Occ, DynVGPRBlockSizeEff);
       unsigned MaxS =
           AMDGPU::IsaInfo::getMaxNumSGPRs(STI, Occ, /*Addressable=*/true);
       outs() << format("  %-14u %-14u %-14u\n", Occ, MaxV, MaxS);

>From 4270aa02e60e1c6fef62d305f001cb3d8a237862 Mon Sep 17 00:00:00 2001
From: mselehov <mselehov at amd.com>
Date: Mon, 13 Jul 2026 02:54:50 -0500
Subject: [PATCH 3/3] [llvm-calc-occupancy] Add tool to check-llvm test
 dependencies

The lit test runs under REQUIRES: amdgpu-registered-target and the tool
substitution is registered as unresolved="ignore", so on a clean build
where llvm-calc-occupancy was not built the RUN line executed the literal
command and failed with exit code 127. Add the tool to LLVM_TEST_DEPENDS
(guarded on the target, since it is only built with the AMDGPU target) so
check-llvm builds it before running the tests.

Assisted-by: Cursor
---
 llvm/test/CMakeLists.txt | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt
index 201f0ee86d7ff..3f1ee7c18bde7 100644
--- a/llvm/test/CMakeLists.txt
+++ b/llvm/test/CMakeLists.txt
@@ -187,6 +187,11 @@ if (TARGET llvm-mt)
   list(APPEND LLVM_TEST_DEPENDS llvm-mt)
 endif ()
 
+# Only built when the AMDGPU target is enabled.
+if (TARGET llvm-calc-occupancy)
+  list(APPEND LLVM_TEST_DEPENDS llvm-calc-occupancy)
+endif ()
+
 if(LLVM_INCLUDE_EXAMPLES)
   list(APPEND LLVM_TEST_DEPENDS
     Kaleidoscope-Ch3



More information about the llvm-commits mailing list