[llvm] [TableGen] Split large printInstruction() for NVPTX to avoid compiler limits (PR #218530)

Malavika Samak via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 3 20:00:43 PDT 2026


https://github.com/malavikasamak updated https://github.com/llvm/llvm-project/pull/218530

>From 4d7981dc82bd831374056781df259747e1bb690c Mon Sep 17 00:00:00 2001
From: Malavika Samak <mmalavika at nvidia.com>
Date: Wed, 2 Sep 2026 13:54:11 -0700
Subject: [PATCH 1/5] [TableGen] Emit function-pointer table for AsmWriter
 overflow instructions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem
-------
For targets with many instructions (NVPTX in particular), the overflow switch
emitted inside printInstruction() for operand sequences that cannot be encoded
in the table-driven bit-packing path can grow large enough to trigger:

  - MSVC C1001/C1053 ICE on AArch64 (function body too large)
  - R_RISCV_JAL relocation truncated to fit (JAL ±1 MB range exceeded)

Solution
--------
Move each unique overflow operand sequence into a small static printPattern_N
function and dispatch through a const function-pointer table indexed by opcode,
replacing the switch entirely:

- Add a `Receiver` parameter to `AsmWriterOperand::getCode()` so that code
  emitted inside static free functions calls `P->printOperand(...)` rather
  than the member function directly.
- `EmitOpcodePatternTable()`: collects overflow instructions, deduplicates
  identical operand sequences into shared `printPattern_N` statics, and emits
  a `<Target>InstPrinters[]` const function-pointer table covering every
  opcode. Non-overflow opcodes point to a no-op `printPattern_None`.
- `EmitOpcodePatternDispatch()`: replaces the overflow switch with a single
  unconditional indirect call through the table, keeping printInstruction()
  small regardless of instruction-set size.

The mechanism triggers for any target with overflow instructions, so it is not
NVPTX-specific. A follow-up can generalize the threshold-based triggering to
any target that exceeds a function-size limit.
---
 llvm/include/llvm/Target/Target.td           |   7 +
 llvm/lib/Target/NVPTX/NVPTX.td               |   1 +
 llvm/utils/TableGen/AsmWriterEmitter.cpp     | 151 ++++++++++++++++---
 llvm/utils/TableGen/Common/AsmWriterInst.cpp |   5 +-
 llvm/utils/TableGen/Common/AsmWriterInst.h   |   5 +-
 5 files changed, 149 insertions(+), 20 deletions(-)

diff --git a/llvm/include/llvm/Target/Target.td b/llvm/include/llvm/Target/Target.td
index 53139e9a2bfeae0..0c969a4cb351ea4 100644
--- a/llvm/include/llvm/Target/Target.td
+++ b/llvm/include/llvm/Target/Target.td
@@ -2060,6 +2060,13 @@ class AsmWriter {
   // will specify which alternative to use. For example "{x|y|z}" with Variant
   // == 1, will expand to "y".
   int Variant = 0;
+
+  // UseFunctionPointerTable - If set to 1, emit a function-pointer table
+  // instead of a switch statement for instructions that overflow the 64-bit
+  // table-driven printer.  This keeps printInstruction() small, avoiding
+  // MSVC C1001/C1053 ICEs and R_RISCV_JAL relocation-range overflows on
+  // targets with large instruction sets.
+  bit UseFunctionPointerTable = 0;
 }
 def DefaultAsmWriter : AsmWriter;
 
diff --git a/llvm/lib/Target/NVPTX/NVPTX.td b/llvm/lib/Target/NVPTX/NVPTX.td
index f9be7928ad1026c..67982d058963729 100644
--- a/llvm/lib/Target/NVPTX/NVPTX.td
+++ b/llvm/lib/Target/NVPTX/NVPTX.td
@@ -140,6 +140,7 @@ def NVPTXInstrInfo : InstrInfo {
 
 def NVPTXAsmWriter : AsmWriter {
   int PassSubtarget = 1;
+  bit UseFunctionPointerTable = 1;
 }
 
 def NVPTX : Target {
diff --git a/llvm/utils/TableGen/AsmWriterEmitter.cpp b/llvm/utils/TableGen/AsmWriterEmitter.cpp
index f3e04bb0f0849e4..62834fa321f3660 100644
--- a/llvm/utils/TableGen/AsmWriterEmitter.cpp
+++ b/llvm/utils/TableGen/AsmWriterEmitter.cpp
@@ -91,6 +91,16 @@ class AsmWriterEmitter {
                                  std::vector<std::vector<unsigned>> &InstIdxs,
                                  std::vector<unsigned> &InstOpsUsed,
                                  bool PassSubtarget) const;
+
+  // Emit a flat function-pointer table covering all opcodes.  Each unique
+  // operand-printing sequence in the overflow set becomes one printPattern_N
+  // static helper; instructions sharing a sequence share the same helper.
+  // The table is indexed directly by getOpcode(), so dispatch is a single
+  // unconditional indirect call with no switch or branch.
+  void EmitOpcodePatternTable(raw_ostream &O, StringRef TargetName,
+                              StringRef ClassName, bool PassSubtarget);
+  void EmitOpcodePatternDispatch(raw_ostream &O, StringRef TargetName,
+                                 StringRef ClassName, bool PassSubtarget);
 };
 
 } // end anonymous namespace
@@ -510,6 +520,21 @@ void AsmWriterEmitter::EmitPrintInstruction(
   const Record *AsmWriter = Target.getAsmWriter();
   StringRef ClassName = AsmWriter->getValueAsString("AsmWriterClassName");
   bool PassSubtarget = AsmWriter->getValueAsInt("PassSubtarget");
+  bool UseFnPtrTable = AsmWriter->getValueAsBit("UseFunctionPointerTable");
+
+  // Delete instructions with no operand info left so that the emptiness check
+  // below only considers overflow instructions, and EmitOpcodePatternTable does
+  // not need to filter them again.
+  llvm::erase_if(Instructions,
+                 [](AsmWriterInst &Inst) { return Inst.Operands.empty(); });
+
+  // When opt-in: pre-emit static printPattern_N helpers and function-pointer
+  // table before printInstruction().  A single indirect call in
+  // printInstruction() then replaces the overflow switch, keeping the function
+  // body small and avoiding MSVC C1001/C1053 ICEs and R_RISCV_JAL
+  // relocation-range overflows that arise on large instruction sets.
+  if (UseFnPtrTable && !Instructions.empty())
+    EmitOpcodePatternTable(O, Target.getName(), ClassName, PassSubtarget);
 
   // This function has some huge switch statements that causing excessive
   // compile time in LLVM profile instrumenation build. This print function
@@ -575,33 +600,125 @@ void AsmWriterEmitter::EmitPrintInstruction(
     BitsLeft -= NumBits;
   }
 
-  // Okay, delete instructions with no operand info left.
-  llvm::erase_if(Instructions,
-                 [](AsmWriterInst &Inst) { return Inst.Operands.empty(); });
-
-  // Because this is a vector, we want to emit from the end.  Reverse all of the
-  // elements in the vector.
-  std::reverse(Instructions.begin(), Instructions.end());
-
-  std::vector<InstructionGroup> Groups = groupInstructions(Instructions);
-
   // Now that we've emitted all of the operand info that fit into 64 bits, emit
   // information for those instructions that are left.  This is a less dense
   // encoding, but we expect the main 64-bit table to handle the majority of
   // instructions.
-  if (!Groups.empty()) {
-    // Find the opcode # of inline asm.
-    O << "  switch (MI->getOpcode()) {\n";
-    O << "  default: llvm_unreachable(\"Unexpected opcode.\");\n";
-    for (const InstructionGroup &G : Groups)
-      emitInstructions(G, O, PassSubtarget);
+  if (!Instructions.empty()) {
+    if (UseFnPtrTable) {
+      // Single unconditional indirect call; the function-pointer table was
+      // emitted above, before printInstruction().
+      EmitOpcodePatternDispatch(O, Target.getName(), ClassName, PassSubtarget);
+    } else {
+      // Because this is a vector, we want to emit from the end.  Reverse all
+      // of the elements in the vector.
+      std::reverse(Instructions.begin(), Instructions.end());
 
-    O << "  }\n";
+      std::vector<InstructionGroup> Groups = groupInstructions(Instructions);
+
+      O << "  switch (MI->getOpcode()) {\n";
+      O << "  default: llvm_unreachable(\"Unexpected opcode.\");\n";
+      for (const InstructionGroup &G : Groups)
+        emitInstructions(G, O, PassSubtarget);
+
+      O << "  }\n";
+    }
   }
 
   O << "}\n";
 }
 
+void AsmWriterEmitter::EmitOpcodePatternTable(raw_ostream &O,
+                                              StringRef TargetName,
+                                              StringRef ClassName,
+                                              bool PassSubtarget) {
+  assert(!Instructions.empty() && "caller should have checked HasOverflow");
+
+  // Serialize each instruction's complete operand sequence into a string key.
+  // Instructions with identical keys share one printPattern_N function.
+  // getCode() with Receiver="P->" turns member calls like printOperand(...)
+  // into P->printOperand(...), which is correct for a static free function.
+  using PatternBody = std::vector<std::string>;
+  std::map<PatternBody, unsigned> PatternMap;
+  SmallVector<PatternBody, 64> Patterns;
+
+  // Table: index by CGIIndex, value = pattern index (~0U for non-overflow).
+  std::vector<unsigned> OpcodeToPattern(NumberedInstructions.size(), ~0U);
+
+  for (const AsmWriterInst &AWI : Instructions) {
+    PatternBody Body;
+    for (const AsmWriterOperand &Op : AWI.Operands)
+      Body.push_back(Op.getCode(PassSubtarget, "P->"));
+    auto [It, Inserted] = PatternMap.emplace(Body, Patterns.size());
+    if (Inserted)
+      Patterns.push_back(Body);
+    OpcodeToPattern[AWI.CGIIndex] = It->second;
+  }
+
+  std::string FullClassName = (TargetName + ClassName).str();
+  // Include ClassName in the table name so multiple AsmWriter variants in the
+  // same TU (e.g. AArch64GenAsmWriter.inc + AArch64GenAsmWriter1.inc) don't
+  // produce conflicting variable declarations.
+  std::string PtrTableName = FullClassName + "Printers";
+  std::string ParamList = "    " + FullClassName +
+                          " *, const MCInst *,\n"
+                          "    uint64_t," +
+                          (PassSubtarget ? " const MCSubtargetInfo &," : "") +
+                          " raw_ostream &";
+
+  // printPattern_None: no-op for instructions fully handled by table-driven
+  // path.
+  O << "static void printPattern_None(\n" << ParamList << ") {}\n\n";
+
+  // One printPattern_N per unique operand sequence.
+  for (unsigned PIdx = 0, E = Patterns.size(); PIdx < E; ++PIdx) {
+    O << "static void printPattern_" << PIdx << "(\n"
+      << "    " << FullClassName << " *P, const MCInst *MI,\n"
+      << "    uint64_t Address,";
+    if (PassSubtarget)
+      O << " const MCSubtargetInfo &STI,";
+    O << " raw_ostream &O) {\n";
+    for (const std::string &Line : Patterns[PIdx])
+      O << "  " << Line << "\n";
+    O << "}\n\n";
+  }
+
+  // Function-pointer table indexed by opcode, covering the full opcode range.
+  // Non-overflow opcodes → printPattern_None.
+  // Overflow opcodes     → their deduplicated printPattern_N.
+  O << "static void (*const " << PtrTableName << "[])(\n"
+    << ParamList << ") = {\n";
+  for (unsigned i = 0, E = NumberedInstructions.size(); i < E; ++i) {
+    unsigned PIdx = OpcodeToPattern[i];
+    if (PIdx == ~0U)
+      O << "  &printPattern_None";
+    else
+      O << "  &printPattern_" << PIdx;
+    if (i + 1 < E)
+      O << ",";
+    O << "\t// " << NumberedInstructions[i]->getName() << "\n";
+  }
+  O << "};\n\n";
+
+  LLVM_DEBUG(dbgs() << "[AsmWriter] " << TargetName << ": "
+                    << OpcodeInsts.size() << " overflow instructions -> "
+                    << Patterns.size() << " unique patterns\n");
+}
+
+void AsmWriterEmitter::EmitOpcodePatternDispatch(raw_ostream &O,
+                                                 StringRef TargetName,
+                                                 StringRef ClassName,
+                                                 bool PassSubtarget) {
+  // Single unconditional indirect call; no switch, no branch.
+  // The compiler cannot inline through a const function pointer, so
+  // printInstruction() itself stays small regardless of instruction-set size.
+  std::string PtrTableName = (TargetName + ClassName + "Printers").str();
+  O << "  " << PtrTableName << "[MI->getOpcode()](this, MI, Address, ";
+  if (PassSubtarget)
+    O << "STI, ";
+  O << "O);\n";
+}
+
 static void
 emitRegisterNameString(raw_ostream &O, StringRef AltName,
                        const std::deque<CodeGenRegister> &Registers) {
diff --git a/llvm/utils/TableGen/Common/AsmWriterInst.cpp b/llvm/utils/TableGen/Common/AsmWriterInst.cpp
index 97430a1eec8a1e5..4afdb67bd784447 100644
--- a/llvm/utils/TableGen/Common/AsmWriterInst.cpp
+++ b/llvm/utils/TableGen/Common/AsmWriterInst.cpp
@@ -20,7 +20,8 @@ using namespace llvm;
 
 static bool isIdentChar(char C) { return isAlnum(C) || C == '_'; }
 
-std::string AsmWriterOperand::getCode(bool PassSubtarget) const {
+std::string AsmWriterOperand::getCode(bool PassSubtarget,
+                                      StringRef Receiver) const {
   if (OperandType == isLiteralTextOperand) {
     if (Str.size() == 1)
       return "O << '" + Str + "';";
@@ -30,7 +31,7 @@ std::string AsmWriterOperand::getCode(bool PassSubtarget) const {
   if (OperandType == isLiteralStatementOperand)
     return Str;
 
-  std::string Result = Str + "(MI";
+  std::string Result = Receiver.str() + Str + "(MI";
   if (PCRel)
     Result += ", Address";
   if (MIOpNo != ~0U)
diff --git a/llvm/utils/TableGen/Common/AsmWriterInst.h b/llvm/utils/TableGen/Common/AsmWriterInst.h
index 26745a8459570ab..fed7825fbfe65bb 100644
--- a/llvm/utils/TableGen/Common/AsmWriterInst.h
+++ b/llvm/utils/TableGen/Common/AsmWriterInst.h
@@ -16,6 +16,7 @@
 #ifndef LLVM_UTILS_TABLEGEN_COMMON_ASMWRITERINST_H
 #define LLVM_UTILS_TABLEGEN_COMMON_ASMWRITERINST_H
 
+#include "llvm/ADT/StringRef.h"
 #include <string>
 #include <vector>
 
@@ -74,7 +75,9 @@ struct AsmWriterOperand {
   }
 
   /// getCode - Return the code that prints this operand.
-  std::string getCode(bool PassSubtarget) const;
+  /// Receiver, if non-empty, is prepended to member-function calls (e.g.
+  /// "P->" turns "printOperand(...)" into "P->printOperand(...)").
+  std::string getCode(bool PassSubtarget, StringRef Receiver = "") const;
 };
 
 class AsmWriterInst {

>From d8d4f7c086b5e22b4b42bb7c35af4cb97e6b5fa9 Mon Sep 17 00:00:00 2001
From: Malavika Samak <mmalavika at nvidia.com>
Date: Mon, 28 Sep 2026 12:05:09 -0700
Subject: [PATCH 2/5] [TableGen] Replace pattern-function dispatch with
 bytecode interpreter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Replace the EmitOpcodePatternTable/EmitOpcodePatternDispatch
(printPattern_N static helpers + direct-call switch) approach with a
two-table bytecode interpreter:

  OpcodeToOffset[]   (uint32_t) — maps opcode → start offset in program
  OverflowProgram[]  (uint16_t) — concatenated bytecode sequences

Each sequence is a list of statement indices terminated by index 1
(return). The interpreter loop in printInstruction() executes the
sequence for the current opcode using a switch on statement index.

Advantages over the direct-call switch approach:
- No static helper functions emitted before printInstruction()
- Statements execute directly inside printInstruction(), so local
  variables (StringTable etc.) are in scope without IIFE hacks
- uint16_t statement indices support >254 unique statements
- Plain integer tables introduce no relocations

Co-Authored-By: Claude Sonnet 4.6 <noreply at anthropic.com>
---
 llvm/utils/TableGen/AsmWriterEmitter.cpp | 184 +++++++++++------------
 1 file changed, 90 insertions(+), 94 deletions(-)

diff --git a/llvm/utils/TableGen/AsmWriterEmitter.cpp b/llvm/utils/TableGen/AsmWriterEmitter.cpp
index 62834fa321f3660..6905ee69b9a7b3a 100644
--- a/llvm/utils/TableGen/AsmWriterEmitter.cpp
+++ b/llvm/utils/TableGen/AsmWriterEmitter.cpp
@@ -92,15 +92,11 @@ class AsmWriterEmitter {
                                  std::vector<unsigned> &InstOpsUsed,
                                  bool PassSubtarget) const;
 
-  // Emit a flat function-pointer table covering all opcodes.  Each unique
-  // operand-printing sequence in the overflow set becomes one printPattern_N
-  // static helper; instructions sharing a sequence share the same helper.
-  // The table is indexed directly by getOpcode(), so dispatch is a single
-  // unconditional indirect call with no switch or branch.
-  void EmitOpcodePatternTable(raw_ostream &O, StringRef TargetName,
-                              StringRef ClassName, bool PassSubtarget);
-  void EmitOpcodePatternDispatch(raw_ostream &O, StringRef TargetName,
-                                 StringRef ClassName, bool PassSubtarget);
+  // Bytecode interpreter state for overflow instructions.
+  std::vector<std::string> BytecodeStmts;
+  void EmitOverflowBytecodeTables(raw_ostream &O, StringRef ClassName,
+                                  bool PassSubtarget);
+  void EmitOverflowBytecodeInterpreter(raw_ostream &O, bool PassSubtarget);
 };
 
 } // end anonymous namespace
@@ -523,18 +519,15 @@ void AsmWriterEmitter::EmitPrintInstruction(
   bool UseFnPtrTable = AsmWriter->getValueAsBit("UseFunctionPointerTable");
 
   // Delete instructions with no operand info left so that the emptiness check
-  // below only considers overflow instructions, and EmitOpcodePatternTable does
-  // not need to filter them again.
+  // below only considers overflow instructions.
   llvm::erase_if(Instructions,
                  [](AsmWriterInst &Inst) { return Inst.Operands.empty(); });
 
-  // When opt-in: pre-emit static printPattern_N helpers and function-pointer
-  // table before printInstruction().  A single indirect call in
-  // printInstruction() then replaces the overflow switch, keeping the function
-  // body small and avoiding MSVC C1001/C1053 ICEs and R_RISCV_JAL
-  // relocation-range overflows that arise on large instruction sets.
+  // When opt-in: emit two plain-integer tables before printInstruction() and
+  // replace the overflow switch with a small bytecode interpreter loop, keeping
+  // the function body small and avoiding MSVC C1001/C1053 ICEs.
   if (UseFnPtrTable && !Instructions.empty())
-    EmitOpcodePatternTable(O, Target.getName(), ClassName, PassSubtarget);
+    EmitOverflowBytecodeTables(O, ClassName, PassSubtarget);
 
   // This function has some huge switch statements that causing excessive
   // compile time in LLVM profile instrumenation build. This print function
@@ -605,10 +598,8 @@ void AsmWriterEmitter::EmitPrintInstruction(
   // encoding, but we expect the main 64-bit table to handle the majority of
   // instructions.
   if (!Instructions.empty()) {
-    if (UseFnPtrTable) {
-      // Single unconditional indirect call; the function-pointer table was
-      // emitted above, before printInstruction().
-      EmitOpcodePatternDispatch(O, Target.getName(), ClassName, PassSubtarget);
+    if (UseFnPtrTable && BytecodeStmts.size() > 2) {
+      EmitOverflowBytecodeInterpreter(O, PassSubtarget);
     } else {
       // Because this is a vector, we want to emit from the end.  Reverse all
       // of the elements in the vector.
@@ -628,95 +619,100 @@ void AsmWriterEmitter::EmitPrintInstruction(
   O << "}\n";
 }
 
-void AsmWriterEmitter::EmitOpcodePatternTable(raw_ostream &O,
-                                              StringRef TargetName,
-                                              StringRef ClassName,
-                                              bool PassSubtarget) {
-  assert(!Instructions.empty() && "caller should have checked HasOverflow");
+void AsmWriterEmitter::EmitOverflowBytecodeTables(raw_ostream &O,
+                                                  StringRef ClassName,
+                                                  bool PassSubtarget) {
+  if (Instructions.empty())
+    return;
+
+  StringRef TargetName = Target.getName();
 
-  // Serialize each instruction's complete operand sequence into a string key.
-  // Instructions with identical keys share one printPattern_N function.
-  // getCode() with Receiver="P->" turns member calls like printOperand(...)
-  // into P->printOperand(...), which is correct for a static free function.
-  using PatternBody = std::vector<std::string>;
-  std::map<PatternBody, unsigned> PatternMap;
-  SmallVector<PatternBody, 64> Patterns;
+  // Assign uint16_t indices to unique atomic operand-print statements.
+  // Index 0 = unexpected-opcode sentinel, 1 = return terminator, 2+ = real.
+  BytecodeStmts.clear();
+  BytecodeStmts.push_back("llvm_unreachable(\"Unexpected opcode.\");");
+  BytecodeStmts.push_back("return;");
+  std::map<std::string, uint16_t> StmtMap;
 
-  // Table: index by CGIIndex, value = pattern index (~0U for non-overflow).
-  std::vector<unsigned> OpcodeToPattern(NumberedInstructions.size(), ~0U);
+  auto GetStmtIdx = [&](const std::string &Code) -> uint16_t {
+    auto [It, Inserted] = StmtMap.emplace(Code, (uint16_t)BytecodeStmts.size());
+    if (Inserted)
+      BytecodeStmts.push_back(Code);
+    return It->second;
+  };
+
+  // Build bytecode sequences and concatenate into OverflowProgram.
+  // Offset 0 holds the unexpected-opcode sentinel; non-overflow opcodes map
+  // there so the interpreter fires llvm_unreachable if ever reached.
+  using Sequence = std::vector<uint16_t>;
+  std::map<Sequence, uint32_t> SeqToOffset;
+  std::vector<uint16_t> Program = {0}; // sentinel at offset 0
+  std::vector<uint32_t> OpcodeToOffset(NumberedInstructions.size(), 0);
 
   for (const AsmWriterInst &AWI : Instructions) {
-    PatternBody Body;
+    Sequence Seq;
     for (const AsmWriterOperand &Op : AWI.Operands)
-      Body.push_back(Op.getCode(PassSubtarget, "P->"));
-    auto [It, Inserted] = PatternMap.emplace(Body, Patterns.size());
+      Seq.push_back(GetStmtIdx(Op.getCode(PassSubtarget)));
+    Seq.push_back(1); // terminator
+
+    auto [It, Inserted] = SeqToOffset.emplace(Seq, (uint32_t)Program.size());
     if (Inserted)
-      Patterns.push_back(Body);
-    OpcodeToPattern[AWI.CGIIndex] = It->second;
+      Program.insert(Program.end(), Seq.begin(), Seq.end());
+    OpcodeToOffset[AWI.CGIIndex] = It->second;
   }
 
-  std::string FullClassName = (TargetName + ClassName).str();
-  // Include ClassName in the table name so multiple AsmWriter variants in the
-  // same TU (e.g. AArch64GenAsmWriter.inc + AArch64GenAsmWriter1.inc) don't
-  // produce conflicting variable declarations.
-  std::string PtrTableName = FullClassName + "Printers";
-  std::string ParamList = "    " + FullClassName +
-                          " *, const MCInst *,\n"
-                          "    uint64_t," +
-                          (PassSubtarget ? " const MCSubtargetInfo &," : "") +
-                          " raw_ostream &";
-
-  // printPattern_None: no-op for instructions fully handled by table-driven
-  // path.
-  O << "static void printPattern_None(\n" << ParamList << ") {}\n\n";
-
-  // One printPattern_N per unique operand sequence.
-  for (unsigned PIdx = 0, E = Patterns.size(); PIdx < E; ++PIdx) {
-    O << "static void printPattern_" << PIdx << "(\n"
-      << "    " << FullClassName << " *P, const MCInst *MI,\n"
-      << "    uint64_t Address,";
-    if (PassSubtarget)
-      O << " const MCSubtargetInfo &STI,";
-    O << " raw_ostream &O) {\n";
-    for (const std::string &Line : Patterns[PIdx])
-      O << "  " << Line << "\n";
-    O << "}\n\n";
-  }
+  // Emit OpcodeToOffset table.
+  O << "// Maps each opcode to its starting index in " << TargetName
+    << "OverflowProgram.\n"
+    << "// " << NumberedInstructions.size() << " entries ("
+    << Instructions.size() << " overflow instructions, " << SeqToOffset.size()
+    << " unique sequences).\n"
+    << "static const uint32_t " << TargetName << "OpcodeToOffset[] = {\n";
+  for (unsigned I = 0; I < NumberedInstructions.size(); ++I)
+    O << "  " << OpcodeToOffset[I] << ",\t// "
+      << NumberedInstructions[I]->getName() << "\n";
+  O << "};\n\n";
 
-  // Function-pointer table indexed by opcode, covering the full opcode range.
-  // Non-overflow opcodes → printPattern_None.
-  // Overflow opcodes     → their deduplicated printPattern_N.
-  O << "static void (*const " << PtrTableName << "[])(\n"
-    << ParamList << ") = {\n";
-  for (unsigned i = 0, E = NumberedInstructions.size(); i < E; ++i) {
-    unsigned PIdx = OpcodeToPattern[i];
-    if (PIdx == ~0U)
-      O << "  &printPattern_None";
+  // Emit OverflowProgram bytecode table with offset annotations at sequence
+  // boundaries.
+  std::set<uint32_t> Boundaries;
+  for (auto &[Seq, Off] : SeqToOffset)
+    Boundaries.insert(Off);
+  O << "// Concatenated operand-printing bytecode sequences (terminated by "
+       "1).\n"
+    << "// " << SeqToOffset.size() << " unique sequences, " << Program.size()
+    << " uint16_t entries total.\n"
+    << "static const uint16_t " << TargetName << "OverflowProgram[] = {\n";
+  for (unsigned I = 0; I < Program.size(); ++I) {
+    if (Boundaries.count(I))
+      O << "  /* " << I << " */ " << (unsigned)Program[I] << ",\n";
     else
-      O << "  &printPattern_" << PIdx;
-    if (i + 1 < E)
-      O << ",";
-    O << "\t// " << NumberedInstructions[i]->getName() << "\n";
+      O << "  " << (unsigned)Program[I] << ",\n";
   }
   O << "};\n\n";
 
   LLVM_DEBUG(dbgs() << "[AsmWriter] " << TargetName << ": "
-                    << OpcodeInsts.size() << " overflow instructions -> "
-                    << Patterns.size() << " unique patterns\n");
+                    << Instructions.size() << " overflow instructions -> "
+                    << SeqToOffset.size() << " unique sequences, "
+                    << BytecodeStmts.size() << " unique statements\n");
 }
 
-void AsmWriterEmitter::EmitOpcodePatternDispatch(raw_ostream &O,
-                                                 StringRef TargetName,
-                                                 StringRef ClassName,
-                                                 bool PassSubtarget) {
-  // Single unconditional indirect call; no switch, no branch.
-  // The compiler cannot inline through a const function pointer, so
-  // printInstruction() itself stays small regardless of instruction-set size.
-  std::string PtrTableName = (TargetName + ClassName + "Printers").str();
-  O << "  " << PtrTableName << "[MI->getOpcode()](this, MI, Address, ";
-  if (PassSubtarget)
-    O << "STI, ";
-  O << "O);\n";
+void AsmWriterEmitter::EmitOverflowBytecodeInterpreter(raw_ostream &O,
+                                                       bool PassSubtarget) {
+  StringRef TargetName = Target.getName();
+  // Execute the bytecode sequence for this opcode until the return terminator.
+  O << "  for (uint32_t Idx = " << TargetName
+    << "OpcodeToOffset[MI->getOpcode()];; ++Idx) {\n"
+    << "    switch (" << TargetName << "OverflowProgram[Idx]) {\n"
+    << "    default: llvm_unreachable(\"Unexpected bytecode command.\");\n";
+  for (unsigned I = 0; I < BytecodeStmts.size(); ++I) {
+    O << "    case " << I << ":\n"
+      << "      " << BytecodeStmts[I] << "\n";
+    // "return;" exits printInstruction(); no break needed.
+    if (BytecodeStmts[I] != "return;")
+      O << "      break;\n";
+  }
+  O << "    }\n  }\n";
 }
 
 static void

>From 0c7212e2bc9a37bc2ba8ea79c82b6afa5e8bc500 Mon Sep 17 00:00:00 2001
From: Malavika Samak <mmalavika at nvidia.com>
Date: Wed, 30 Sep 2026 11:29:22 -0700
Subject: [PATCH 3/5] [TableGen] Address review feedback on bytecode
 interpreter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Rename UseFunctionPointerTable -> UseBytecode in Target.td/NVPTX.td
- Revert Receiver parameter from AsmWriterOperand::getCode() (not needed:
  the interpreter runs inside printInstruction(), not in static helpers)
- Add ExactMatch param to groupInstructions(): when true, only groups
  instructions with identical operand sequences (used by bytecode path)
- Replace EmitOverflowBytecodeTables + EmitOverflowBytecodeInterpreter
  with a single static EmitOverflowBytecodeSection(); tables are static
  const arrays emitted inside printInstruction()
- Pre-seed Commands MapVector with sentinel (index 0 = unreachable) and
  return terminator (index 1 = "return;") to avoid duplicate entry from
  AWI's trailing "return;" operand
- Use getMinimalTypeForRange() for OpcodeToOffset and OverflowProgram
  element types instead of hardcoded uint32_t/uint16_t
- Add "return;" guard at OverflowProgram[1]: in NDEBUG builds
  llvm_unreachable() is a no-op; the for-loop would increment past the
  sentinel at offset 0 and execute the wrong sequence; offset 1 holding
  index 1 ("return;") ensures safe exit instead
- Check Idx != 1 (reserved slot) rather than Stmt != "return;" to
  suppress the break — avoids fragility if a literal-statement operand
  happens to contain "return;"
---
 llvm/include/llvm/Target/Target.td           |  10 +-
 llvm/lib/Target/NVPTX/NVPTX.td               |   2 +-
 llvm/utils/TableGen/AsmWriterEmitter.cpp     | 183 ++++++++-----------
 llvm/utils/TableGen/Common/AsmWriterInst.cpp |   5 +-
 llvm/utils/TableGen/Common/AsmWriterInst.h   |   4 +-
 5 files changed, 84 insertions(+), 120 deletions(-)

diff --git a/llvm/include/llvm/Target/Target.td b/llvm/include/llvm/Target/Target.td
index 0c969a4cb351ea4..bcb89aa9048a207 100644
--- a/llvm/include/llvm/Target/Target.td
+++ b/llvm/include/llvm/Target/Target.td
@@ -2061,12 +2061,10 @@ class AsmWriter {
   // == 1, will expand to "y".
   int Variant = 0;
 
-  // UseFunctionPointerTable - If set to 1, emit a function-pointer table
-  // instead of a switch statement for instructions that overflow the 64-bit
-  // table-driven printer.  This keeps printInstruction() small, avoiding
-  // MSVC C1001/C1053 ICEs and R_RISCV_JAL relocation-range overflows on
-  // targets with large instruction sets.
-  bit UseFunctionPointerTable = 0;
+  // If set, emit a bytecode table and interpreter loop instead of a large
+  // switch statement for remaining operands that do not fit in the 64-bit
+  // table-driven printer.
+  bit UseBytecode = 0;
 }
 def DefaultAsmWriter : AsmWriter;
 
diff --git a/llvm/lib/Target/NVPTX/NVPTX.td b/llvm/lib/Target/NVPTX/NVPTX.td
index 67982d058963729..969e8de4c5fdbe1 100644
--- a/llvm/lib/Target/NVPTX/NVPTX.td
+++ b/llvm/lib/Target/NVPTX/NVPTX.td
@@ -140,7 +140,7 @@ def NVPTXInstrInfo : InstrInfo {
 
 def NVPTXAsmWriter : AsmWriter {
   int PassSubtarget = 1;
-  bit UseFunctionPointerTable = 1;
+  bit UseBytecode = 1;
 }
 
 def NVPTX : Target {
diff --git a/llvm/utils/TableGen/AsmWriterEmitter.cpp b/llvm/utils/TableGen/AsmWriterEmitter.cpp
index 6905ee69b9a7b3a..63df6a320422ccb 100644
--- a/llvm/utils/TableGen/AsmWriterEmitter.cpp
+++ b/llvm/utils/TableGen/AsmWriterEmitter.cpp
@@ -20,6 +20,7 @@
 #include "Common/Types.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
@@ -92,11 +93,6 @@ class AsmWriterEmitter {
                                  std::vector<unsigned> &InstOpsUsed,
                                  bool PassSubtarget) const;
 
-  // Bytecode interpreter state for overflow instructions.
-  std::vector<std::string> BytecodeStmts;
-  void EmitOverflowBytecodeTables(raw_ostream &O, StringRef ClassName,
-                                  bool PassSubtarget);
-  void EmitOverflowBytecodeInterpreter(raw_ostream &O, bool PassSubtarget);
 };
 
 } // end anonymous namespace
@@ -121,9 +117,10 @@ PrintCases(std::vector<std::pair<std::string, AsmWriterOperand>> &OpsToPrint,
   O << "\n      break;\n";
 }
 
-/// Group instructions by similarity.
+/// Group instructions by similarity.  When ExactMatch is true, only group
+/// instructions with identical operand sequences (for the bytecode path).
 static std::vector<InstructionGroup>
-groupInstructions(std::vector<AsmWriterInst> &Insts) {
+groupInstructions(std::vector<AsmWriterInst> &Insts, bool ExactMatch = false) {
   std::vector<InstructionGroup> Groups;
   while (!Insts.empty()) {
     InstructionGroup &G = Groups.emplace_back(std::move(Insts.back()));
@@ -131,7 +128,7 @@ groupInstructions(std::vector<AsmWriterInst> &Insts) {
 
     for (unsigned I = Insts.size(); I != 0; --I) {
       unsigned DiffOp = Insts[I - 1].MatchesAllButOneOp(G.FirstInst);
-      if (DiffOp != ~1U) {
+      if (DiffOp == ~0U || (!ExactMatch && DiffOp != ~1U)) {
         if (G.DifferingOperand == ~0U) // First match!
           G.DifferingOperand = DiffOp;
 
@@ -505,6 +502,11 @@ void AsmWriterEmitter::EmitGetMnemonic(
   O << "}\n";
 }
 
+static void EmitOverflowBytecodeSection(raw_ostream &O, StringRef TargetName,
+                                        const std::vector<InstructionGroup> &,
+                                        ArrayRef<const CodeGenInstruction *>,
+                                        bool PassSubtarget);
+
 /// EmitPrintInstruction - Generate the code for the "printInstruction" method
 /// implementation. Destroys all instances of AsmWriterInst information, by
 /// clearing the Instructions vector.
@@ -516,19 +518,13 @@ void AsmWriterEmitter::EmitPrintInstruction(
   const Record *AsmWriter = Target.getAsmWriter();
   StringRef ClassName = AsmWriter->getValueAsString("AsmWriterClassName");
   bool PassSubtarget = AsmWriter->getValueAsInt("PassSubtarget");
-  bool UseFnPtrTable = AsmWriter->getValueAsBit("UseFunctionPointerTable");
+  bool UseBytecode = AsmWriter->getValueAsBit("UseBytecode");
 
   // Delete instructions with no operand info left so that the emptiness check
   // below only considers overflow instructions.
   llvm::erase_if(Instructions,
                  [](AsmWriterInst &Inst) { return Inst.Operands.empty(); });
 
-  // When opt-in: emit two plain-integer tables before printInstruction() and
-  // replace the overflow switch with a small bytecode interpreter loop, keeping
-  // the function body small and avoiding MSVC C1001/C1053 ICEs.
-  if (UseFnPtrTable && !Instructions.empty())
-    EmitOverflowBytecodeTables(O, ClassName, PassSubtarget);
-
   // This function has some huge switch statements that causing excessive
   // compile time in LLVM profile instrumenation build. This print function
   // usually is not frequently called in compilation. Here we disable the
@@ -598,15 +594,16 @@ void AsmWriterEmitter::EmitPrintInstruction(
   // encoding, but we expect the main 64-bit table to handle the majority of
   // instructions.
   if (!Instructions.empty()) {
-    if (UseFnPtrTable && BytecodeStmts.size() > 2) {
-      EmitOverflowBytecodeInterpreter(O, PassSubtarget);
+    // Because this is a vector, we want to emit from the end.  Reverse all
+    // of the elements in the vector.
+    std::reverse(Instructions.begin(), Instructions.end());
+
+    std::vector<InstructionGroup> Groups =
+        groupInstructions(Instructions, UseBytecode);
+    if (UseBytecode) {
+      EmitOverflowBytecodeSection(O, Target.getName(), Groups,
+                                  NumberedInstructions, PassSubtarget);
     } else {
-      // Because this is a vector, we want to emit from the end.  Reverse all
-      // of the elements in the vector.
-      std::reverse(Instructions.begin(), Instructions.end());
-
-      std::vector<InstructionGroup> Groups = groupInstructions(Instructions);
-
       O << "  switch (MI->getOpcode()) {\n";
       O << "  default: llvm_unreachable(\"Unexpected opcode.\");\n";
       for (const InstructionGroup &G : Groups)
@@ -619,100 +616,72 @@ void AsmWriterEmitter::EmitPrintInstruction(
   O << "}\n";
 }
 
-void AsmWriterEmitter::EmitOverflowBytecodeTables(raw_ostream &O,
-                                                  StringRef ClassName,
-                                                  bool PassSubtarget) {
-  if (Instructions.empty())
-    return;
-
-  StringRef TargetName = Target.getName();
-
-  // Assign uint16_t indices to unique atomic operand-print statements.
-  // Index 0 = unexpected-opcode sentinel, 1 = return terminator, 2+ = real.
-  BytecodeStmts.clear();
-  BytecodeStmts.push_back("llvm_unreachable(\"Unexpected opcode.\");");
-  BytecodeStmts.push_back("return;");
-  std::map<std::string, uint16_t> StmtMap;
-
-  auto GetStmtIdx = [&](const std::string &Code) -> uint16_t {
-    auto [It, Inserted] = StmtMap.emplace(Code, (uint16_t)BytecodeStmts.size());
-    if (Inserted)
-      BytecodeStmts.push_back(Code);
-    return It->second;
-  };
-
-  // Build bytecode sequences and concatenate into OverflowProgram.
-  // Offset 0 holds the unexpected-opcode sentinel; non-overflow opcodes map
-  // there so the interpreter fires llvm_unreachable if ever reached.
-  using Sequence = std::vector<uint16_t>;
-  std::map<Sequence, uint32_t> SeqToOffset;
-  std::vector<uint16_t> Program = {0}; // sentinel at offset 0
-  std::vector<uint32_t> OpcodeToOffset(NumberedInstructions.size(), 0);
-
-  for (const AsmWriterInst &AWI : Instructions) {
-    Sequence Seq;
-    for (const AsmWriterOperand &Op : AWI.Operands)
-      Seq.push_back(GetStmtIdx(Op.getCode(PassSubtarget)));
-    Seq.push_back(1); // terminator
-
-    auto [It, Inserted] = SeqToOffset.emplace(Seq, (uint32_t)Program.size());
-    if (Inserted)
-      Program.insert(Program.end(), Seq.begin(), Seq.end());
-    OpcodeToOffset[AWI.CGIIndex] = It->second;
+/// Emit the bytecode tables and interpreter loop for overflow instructions.
+/// Instructions with identical operand sequences share the same offset (via
+/// groupInstructions with ExactMatch=true).  Tables are emitted as static const
+/// arrays inside printInstruction() so that local variables remain in scope.
+static void
+EmitOverflowBytecodeSection(raw_ostream &O, StringRef TargetName,
+                             const std::vector<InstructionGroup> &Groups,
+                             ArrayRef<const CodeGenInstruction *> NumberedInsts,
+                             bool PassSubtarget) {
+  // Index 0 = unexpected-opcode sentinel, 1 = return terminator (already the
+  // last operand of every AsmWriterInst), 2+ = real print statements.
+  // Pre-seeding ensures AWI.Operands' trailing "return;" maps to index 1.
+  MapVector<std::string, unsigned, StringMap<unsigned>> Commands;
+  Commands.insert({"llvm_unreachable(\"Unexpected opcode.\");", 0});
+  Commands.insert({"return;", 1});
+
+  // OpcodeToOffset[opcode] = starting index in OverflowProgram.
+  // Offset 0 is the unreachable sentinel for non-overflow opcodes.
+  // Offset 1 is a "return;" guard: in NDEBUG builds llvm_unreachable() is a
+  // no-op and the for-loop would increment past the sentinel to Program[1];
+  // having index 1 ("return;") there ensures a safe exit instead of
+  // accidentally executing the first real sequence's bytecodes.
+  std::vector<unsigned> OpcodeToOffset(NumberedInsts.size(), 0);
+  std::vector<unsigned> Program = {0, 1}; // sentinel + NDEBUG return guard
+
+  for (const InstructionGroup &G : Groups) {
+    unsigned Offset = Program.size();
+    OpcodeToOffset[G.FirstInst.CGIIndex] = Offset;
+    for (const AsmWriterInst &AWI : G.SimilarInsts)
+      OpcodeToOffset[AWI.CGIIndex] = Offset;
+    for (const AsmWriterOperand &Op : G.FirstInst.Operands) {
+      auto [It, _] = Commands.try_emplace(Op.getCode(PassSubtarget),
+                                          Commands.size());
+      Program.push_back(It->second);
+    }
   }
 
-  // Emit OpcodeToOffset table.
-  O << "// Maps each opcode to its starting index in " << TargetName
-    << "OverflowProgram.\n"
-    << "// " << NumberedInstructions.size() << " entries ("
-    << Instructions.size() << " overflow instructions, " << SeqToOffset.size()
-    << " unique sequences).\n"
-    << "static const uint32_t " << TargetName << "OpcodeToOffset[] = {\n";
-  for (unsigned I = 0; I < NumberedInstructions.size(); ++I)
-    O << "  " << OpcodeToOffset[I] << ",\t// "
-      << NumberedInstructions[I]->getName() << "\n";
-  O << "};\n\n";
-
-  // Emit OverflowProgram bytecode table with offset annotations at sequence
-  // boundaries.
-  std::set<uint32_t> Boundaries;
-  for (auto &[Seq, Off] : SeqToOffset)
-    Boundaries.insert(Off);
-  O << "// Concatenated operand-printing bytecode sequences (terminated by "
-       "1).\n"
-    << "// " << SeqToOffset.size() << " unique sequences, " << Program.size()
-    << " uint16_t entries total.\n"
-    << "static const uint16_t " << TargetName << "OverflowProgram[] = {\n";
-  for (unsigned I = 0; I < Program.size(); ++I) {
-    if (Boundaries.count(I))
-      O << "  /* " << I << " */ " << (unsigned)Program[I] << ",\n";
-    else
-      O << "  " << (unsigned)Program[I] << ",\n";
-  }
-  O << "};\n\n";
+  StringRef OffsetType = getMinimalTypeForRange(Program.size() - 1);
+  StringRef StmtType = getMinimalTypeForRange(Commands.size() - 1);
 
-  LLVM_DEBUG(dbgs() << "[AsmWriter] " << TargetName << ": "
-                    << Instructions.size() << " overflow instructions -> "
-                    << SeqToOffset.size() << " unique sequences, "
-                    << BytecodeStmts.size() << " unique statements\n");
-}
+  O << "  static const " << OffsetType << " " << TargetName
+    << "OpcodeToOffset[] = {\n";
+  for (unsigned I = 0; I < NumberedInsts.size(); ++I)
+    O << "    " << OpcodeToOffset[I] << ",\n";
+  O << "  };\n";
+
+  O << "  static const " << StmtType << " " << TargetName
+    << "OverflowProgram[] = {\n";
+  for (unsigned Val : Program)
+    O << "    " << Val << ",\n";
+  O << "  };\n";
 
-void AsmWriterEmitter::EmitOverflowBytecodeInterpreter(raw_ostream &O,
-                                                       bool PassSubtarget) {
-  StringRef TargetName = Target.getName();
-  // Execute the bytecode sequence for this opcode until the return terminator.
-  O << "  for (uint32_t Idx = " << TargetName
+  O << "  for (" << OffsetType << " Idx = " << TargetName
     << "OpcodeToOffset[MI->getOpcode()];; ++Idx) {\n"
     << "    switch (" << TargetName << "OverflowProgram[Idx]) {\n"
     << "    default: llvm_unreachable(\"Unexpected bytecode command.\");\n";
-  for (unsigned I = 0; I < BytecodeStmts.size(); ++I) {
-    O << "    case " << I << ":\n"
-      << "      " << BytecodeStmts[I] << "\n";
-    // "return;" exits printInstruction(); no break needed.
-    if (BytecodeStmts[I] != "return;")
+  for (auto &[Stmt, Idx] : Commands) {
+    O << "    case " << Idx << ":\n      " << Stmt << "\n";
+    if (Idx != 1) // index 1 is the reserved "return;" terminator
       O << "      break;\n";
   }
   O << "    }\n  }\n";
+
+  LLVM_DEBUG(dbgs() << "[AsmWriter] " << TargetName << ": " << Groups.size()
+                    << " unique sequences, " << Commands.size()
+                    << " unique statements\n");
 }
 
 static void
diff --git a/llvm/utils/TableGen/Common/AsmWriterInst.cpp b/llvm/utils/TableGen/Common/AsmWriterInst.cpp
index 4afdb67bd784447..97430a1eec8a1e5 100644
--- a/llvm/utils/TableGen/Common/AsmWriterInst.cpp
+++ b/llvm/utils/TableGen/Common/AsmWriterInst.cpp
@@ -20,8 +20,7 @@ using namespace llvm;
 
 static bool isIdentChar(char C) { return isAlnum(C) || C == '_'; }
 
-std::string AsmWriterOperand::getCode(bool PassSubtarget,
-                                      StringRef Receiver) const {
+std::string AsmWriterOperand::getCode(bool PassSubtarget) const {
   if (OperandType == isLiteralTextOperand) {
     if (Str.size() == 1)
       return "O << '" + Str + "';";
@@ -31,7 +30,7 @@ std::string AsmWriterOperand::getCode(bool PassSubtarget,
   if (OperandType == isLiteralStatementOperand)
     return Str;
 
-  std::string Result = Receiver.str() + Str + "(MI";
+  std::string Result = Str + "(MI";
   if (PCRel)
     Result += ", Address";
   if (MIOpNo != ~0U)
diff --git a/llvm/utils/TableGen/Common/AsmWriterInst.h b/llvm/utils/TableGen/Common/AsmWriterInst.h
index fed7825fbfe65bb..5aa72ecdbd5202b 100644
--- a/llvm/utils/TableGen/Common/AsmWriterInst.h
+++ b/llvm/utils/TableGen/Common/AsmWriterInst.h
@@ -75,9 +75,7 @@ struct AsmWriterOperand {
   }
 
   /// getCode - Return the code that prints this operand.
-  /// Receiver, if non-empty, is prepended to member-function calls (e.g.
-  /// "P->" turns "printOperand(...)" into "P->printOperand(...)").
-  std::string getCode(bool PassSubtarget, StringRef Receiver = "") const;
+  std::string getCode(bool PassSubtarget) const;
 };
 
 class AsmWriterInst {

>From 77a2c3f6f9ef2cd9b9d3e107f55e78f34537218b Mon Sep 17 00:00:00 2001
From: Malavika Samak <mmalavika at nvidia.com>
Date: Wed, 30 Sep 2026 13:07:25 -0700
Subject: [PATCH 4/5] [TableGen] Address remaining review feedback on bytecode
 interpreter

- Remove unused StringRef include from AsmWriterInst.h (include was only
  needed for the Receiver parameter, which was reverted in the prior commit)
- Drop Program vector; compute OverflowProgram in two passes over Groups,
  using a running TotalSize counter instead of building Program explicitly
- Apply clang-format fixes
---
 llvm/utils/TableGen/AsmWriterEmitter.cpp   | 34 ++++++++++------------
 llvm/utils/TableGen/Common/AsmWriterInst.h |  1 -
 2 files changed, 16 insertions(+), 19 deletions(-)

diff --git a/llvm/utils/TableGen/AsmWriterEmitter.cpp b/llvm/utils/TableGen/AsmWriterEmitter.cpp
index 63df6a320422ccb..f96ad077ae1a024 100644
--- a/llvm/utils/TableGen/AsmWriterEmitter.cpp
+++ b/llvm/utils/TableGen/AsmWriterEmitter.cpp
@@ -92,7 +92,6 @@ class AsmWriterEmitter {
                                  std::vector<std::vector<unsigned>> &InstIdxs,
                                  std::vector<unsigned> &InstOpsUsed,
                                  bool PassSubtarget) const;
-
 };
 
 } // end anonymous namespace
@@ -622,9 +621,9 @@ void AsmWriterEmitter::EmitPrintInstruction(
 /// arrays inside printInstruction() so that local variables remain in scope.
 static void
 EmitOverflowBytecodeSection(raw_ostream &O, StringRef TargetName,
-                             const std::vector<InstructionGroup> &Groups,
-                             ArrayRef<const CodeGenInstruction *> NumberedInsts,
-                             bool PassSubtarget) {
+                            const std::vector<InstructionGroup> &Groups,
+                            ArrayRef<const CodeGenInstruction *> NumberedInsts,
+                            bool PassSubtarget) {
   // Index 0 = unexpected-opcode sentinel, 1 = return terminator (already the
   // last operand of every AsmWriterInst), 2+ = real print statements.
   // Pre-seeding ensures AWI.Operands' trailing "return;" maps to index 1.
@@ -638,22 +637,19 @@ EmitOverflowBytecodeSection(raw_ostream &O, StringRef TargetName,
   // no-op and the for-loop would increment past the sentinel to Program[1];
   // having index 1 ("return;") there ensures a safe exit instead of
   // accidentally executing the first real sequence's bytecodes.
+  // Real sequences start at offset 2.
   std::vector<unsigned> OpcodeToOffset(NumberedInsts.size(), 0);
-  std::vector<unsigned> Program = {0, 1}; // sentinel + NDEBUG return guard
-
+  unsigned TotalSize = 2; // slot 0 = sentinel, slot 1 = NDEBUG return guard
   for (const InstructionGroup &G : Groups) {
-    unsigned Offset = Program.size();
-    OpcodeToOffset[G.FirstInst.CGIIndex] = Offset;
+    OpcodeToOffset[G.FirstInst.CGIIndex] = TotalSize;
     for (const AsmWriterInst &AWI : G.SimilarInsts)
-      OpcodeToOffset[AWI.CGIIndex] = Offset;
-    for (const AsmWriterOperand &Op : G.FirstInst.Operands) {
-      auto [It, _] = Commands.try_emplace(Op.getCode(PassSubtarget),
-                                          Commands.size());
-      Program.push_back(It->second);
-    }
+      OpcodeToOffset[AWI.CGIIndex] = TotalSize;
+    TotalSize += G.FirstInst.Operands.size();
+    for (const AsmWriterOperand &Op : G.FirstInst.Operands)
+      Commands.try_emplace(Op.getCode(PassSubtarget), Commands.size());
   }
 
-  StringRef OffsetType = getMinimalTypeForRange(Program.size() - 1);
+  StringRef OffsetType = getMinimalTypeForRange(TotalSize - 1);
   StringRef StmtType = getMinimalTypeForRange(Commands.size() - 1);
 
   O << "  static const " << OffsetType << " " << TargetName
@@ -663,9 +659,11 @@ EmitOverflowBytecodeSection(raw_ostream &O, StringRef TargetName,
   O << "  };\n";
 
   O << "  static const " << StmtType << " " << TargetName
-    << "OverflowProgram[] = {\n";
-  for (unsigned Val : Program)
-    O << "    " << Val << ",\n";
+    << "OverflowProgram[] = {\n"
+    << "    0, 1,\n"; // sentinel + NDEBUG return guard
+  for (const InstructionGroup &G : Groups)
+    for (const AsmWriterOperand &Op : G.FirstInst.Operands)
+      O << "    " << Commands.lookup(Op.getCode(PassSubtarget)) << ",\n";
   O << "  };\n";
 
   O << "  for (" << OffsetType << " Idx = " << TargetName
diff --git a/llvm/utils/TableGen/Common/AsmWriterInst.h b/llvm/utils/TableGen/Common/AsmWriterInst.h
index 5aa72ecdbd5202b..26745a8459570ab 100644
--- a/llvm/utils/TableGen/Common/AsmWriterInst.h
+++ b/llvm/utils/TableGen/Common/AsmWriterInst.h
@@ -16,7 +16,6 @@
 #ifndef LLVM_UTILS_TABLEGEN_COMMON_ASMWRITERINST_H
 #define LLVM_UTILS_TABLEGEN_COMMON_ASMWRITERINST_H
 
-#include "llvm/ADT/StringRef.h"
 #include <string>
 #include <vector>
 

>From bf0dfec1e405ab307731cdd15ff5960f726e84f0 Mon Sep 17 00:00:00 2001
From: Malavika Samak <mmalavika at nvidia.com>
Date: Thu, 1 Oct 2026 12:02:11 -0700
Subject: [PATCH 5/5] [TableGen] Address remaining review feedback on bytecode
 interpreter

- Drop TargetName prefix from OpcodeToOffset[] and OverflowProgram[]
  (function-local names need no target disambiguation)
- Add instruction-name comments to OpcodeToOffset[] entries
- Move EmitOverflowBytecodeSection after emitInstructions() to avoid
  the forward declaration
- Move erase_if before std::reverse so the index is stable when we
  remove the first operand
- Emit one InstructionGroup sequence per line in OverflowProgram[] with
  a starting-offset comment; shrinks the table from ~6450 to ~270 lines
  for NVPTX and makes sequence boundaries easy to see
- Start TotalSize at 1 and drop the NDEBUG return guard slot;
  llvm_unreachable() expands to __builtin_unreachable() in NDEBUG builds
  so control never falls through case 0

Co-Authored-By: Claude Sonnet 4.6 <noreply at anthropic.com>
---
 llvm/utils/TableGen/AsmWriterEmitter.cpp | 148 +++++++++++------------
 1 file changed, 71 insertions(+), 77 deletions(-)

diff --git a/llvm/utils/TableGen/AsmWriterEmitter.cpp b/llvm/utils/TableGen/AsmWriterEmitter.cpp
index f96ad077ae1a024..a6599f27e64b293 100644
--- a/llvm/utils/TableGen/AsmWriterEmitter.cpp
+++ b/llvm/utils/TableGen/AsmWriterEmitter.cpp
@@ -185,6 +185,72 @@ static void emitInstructions(const InstructionGroup &G, raw_ostream &O,
   O << "    break;\n";
 }
 
+/// Emit the bytecode tables and interpreter loop for overflow instructions.
+/// Instructions with identical operand sequences share the same offset (via
+/// groupInstructions with ExactMatch=true).  Tables are emitted as static const
+/// arrays inside printInstruction() so that local variables remain in scope.
+static void
+EmitOverflowBytecodeSection(raw_ostream &O, StringRef TargetName,
+                            const std::vector<InstructionGroup> &Groups,
+                            ArrayRef<const CodeGenInstruction *> NumberedInsts,
+                            bool PassSubtarget) {
+  // Bytecode 0 = unexpected-opcode sentinel, 1 = return terminator (already
+  // the last operand of every AsmWriterInst).
+  // Pre-seeding ensures AWI.Operands' trailing "return;" maps to index 1.
+  MapVector<std::string, unsigned, StringMap<unsigned>> Commands;
+  Commands.insert({"llvm_unreachable(\"Unexpected opcode.\");", 0});
+  Commands.insert({"return;", 1});
+
+  // OpcodeToOffset[opcode] = starting index in OverflowProgram.
+  // Offset 0 is the unreachable sentinel for non-overflow opcodes.
+  // Real sequences start at offset 1.
+  std::vector<unsigned> OpcodeToOffset(NumberedInsts.size(), 0);
+  unsigned TotalSize = 1; // slot 0 = sentinel
+  for (const InstructionGroup &G : Groups) {
+    OpcodeToOffset[G.FirstInst.CGIIndex] = TotalSize;
+    for (const AsmWriterInst &AWI : G.SimilarInsts)
+      OpcodeToOffset[AWI.CGIIndex] = TotalSize;
+    TotalSize += G.FirstInst.Operands.size();
+    for (const AsmWriterOperand &Op : G.FirstInst.Operands)
+      Commands.try_emplace(Op.getCode(PassSubtarget), Commands.size());
+  }
+
+  StringRef OffsetType = getMinimalTypeForRange(TotalSize - 1);
+  StringRef StmtType = getMinimalTypeForRange(Commands.size() - 1);
+
+  O << "  static const " << OffsetType << " OpcodeToOffset[] = {\n";
+  for (unsigned I = 0; I < NumberedInsts.size(); ++I)
+    O << "    " << OpcodeToOffset[I] << ",\t// " << NumberedInsts[I]->getName()
+      << "\n";
+  O << "  };\n";
+
+  O << "  static const " << StmtType << " OverflowProgram[] = {\n"
+    << "    /* 0 */ 0,\n"; // sentinel
+  unsigned Offset = 1;
+  for (const InstructionGroup &G : Groups) {
+    O << "    /* " << Offset << " */";
+    for (const AsmWriterOperand &Op : G.FirstInst.Operands)
+      O << " " << Commands.lookup(Op.getCode(PassSubtarget)) << ",";
+    O << "\n";
+    Offset += G.FirstInst.Operands.size();
+  }
+  O << "  };\n";
+
+  O << "  for (" << OffsetType << " Idx = OpcodeToOffset[MI->getOpcode()];; ++Idx) {\n"
+    << "    switch (OverflowProgram[Idx]) {\n"
+    << "    default: llvm_unreachable(\"Unexpected bytecode command.\");\n";
+  for (auto &[Stmt, Idx] : Commands) {
+    O << "    case " << Idx << ":\n      " << Stmt << "\n";
+    if (Idx != 1) // bytecode 1 is the "return;" sequence terminator
+      O << "      break;\n";
+  }
+  O << "    }\n  }\n";
+
+  LLVM_DEBUG(dbgs() << "[AsmWriter] " << TargetName << ": " << Groups.size()
+                    << " unique sequences, " << Commands.size()
+                    << " unique statements\n");
+}
+
 void AsmWriterEmitter::FindUniqueOperandCommands(
     std::vector<std::string> &UniqueOperandCommands,
     std::vector<std::vector<unsigned>> &InstIdxs,
@@ -501,11 +567,6 @@ void AsmWriterEmitter::EmitGetMnemonic(
   O << "}\n";
 }
 
-static void EmitOverflowBytecodeSection(raw_ostream &O, StringRef TargetName,
-                                        const std::vector<InstructionGroup> &,
-                                        ArrayRef<const CodeGenInstruction *>,
-                                        bool PassSubtarget);
-
 /// EmitPrintInstruction - Generate the code for the "printInstruction" method
 /// implementation. Destroys all instances of AsmWriterInst information, by
 /// clearing the Instructions vector.
@@ -519,11 +580,6 @@ void AsmWriterEmitter::EmitPrintInstruction(
   bool PassSubtarget = AsmWriter->getValueAsInt("PassSubtarget");
   bool UseBytecode = AsmWriter->getValueAsBit("UseBytecode");
 
-  // Delete instructions with no operand info left so that the emptiness check
-  // below only considers overflow instructions.
-  llvm::erase_if(Instructions,
-                 [](AsmWriterInst &Inst) { return Inst.Operands.empty(); });
-
   // This function has some huge switch statements that causing excessive
   // compile time in LLVM profile instrumenation build. This print function
   // usually is not frequently called in compilation. Here we disable the
@@ -592,6 +648,11 @@ void AsmWriterEmitter::EmitPrintInstruction(
   // information for those instructions that are left.  This is a less dense
   // encoding, but we expect the main 64-bit table to handle the majority of
   // instructions.
+  // Delete instructions with no operand info left so that the emptiness check
+  // below only considers overflow instructions.
+  llvm::erase_if(Instructions,
+                 [](AsmWriterInst &Inst) { return Inst.Operands.empty(); });
+
   if (!Instructions.empty()) {
     // Because this is a vector, we want to emit from the end.  Reverse all
     // of the elements in the vector.
@@ -615,73 +676,6 @@ void AsmWriterEmitter::EmitPrintInstruction(
   O << "}\n";
 }
 
-/// Emit the bytecode tables and interpreter loop for overflow instructions.
-/// Instructions with identical operand sequences share the same offset (via
-/// groupInstructions with ExactMatch=true).  Tables are emitted as static const
-/// arrays inside printInstruction() so that local variables remain in scope.
-static void
-EmitOverflowBytecodeSection(raw_ostream &O, StringRef TargetName,
-                            const std::vector<InstructionGroup> &Groups,
-                            ArrayRef<const CodeGenInstruction *> NumberedInsts,
-                            bool PassSubtarget) {
-  // Index 0 = unexpected-opcode sentinel, 1 = return terminator (already the
-  // last operand of every AsmWriterInst), 2+ = real print statements.
-  // Pre-seeding ensures AWI.Operands' trailing "return;" maps to index 1.
-  MapVector<std::string, unsigned, StringMap<unsigned>> Commands;
-  Commands.insert({"llvm_unreachable(\"Unexpected opcode.\");", 0});
-  Commands.insert({"return;", 1});
-
-  // OpcodeToOffset[opcode] = starting index in OverflowProgram.
-  // Offset 0 is the unreachable sentinel for non-overflow opcodes.
-  // Offset 1 is a "return;" guard: in NDEBUG builds llvm_unreachable() is a
-  // no-op and the for-loop would increment past the sentinel to Program[1];
-  // having index 1 ("return;") there ensures a safe exit instead of
-  // accidentally executing the first real sequence's bytecodes.
-  // Real sequences start at offset 2.
-  std::vector<unsigned> OpcodeToOffset(NumberedInsts.size(), 0);
-  unsigned TotalSize = 2; // slot 0 = sentinel, slot 1 = NDEBUG return guard
-  for (const InstructionGroup &G : Groups) {
-    OpcodeToOffset[G.FirstInst.CGIIndex] = TotalSize;
-    for (const AsmWriterInst &AWI : G.SimilarInsts)
-      OpcodeToOffset[AWI.CGIIndex] = TotalSize;
-    TotalSize += G.FirstInst.Operands.size();
-    for (const AsmWriterOperand &Op : G.FirstInst.Operands)
-      Commands.try_emplace(Op.getCode(PassSubtarget), Commands.size());
-  }
-
-  StringRef OffsetType = getMinimalTypeForRange(TotalSize - 1);
-  StringRef StmtType = getMinimalTypeForRange(Commands.size() - 1);
-
-  O << "  static const " << OffsetType << " " << TargetName
-    << "OpcodeToOffset[] = {\n";
-  for (unsigned I = 0; I < NumberedInsts.size(); ++I)
-    O << "    " << OpcodeToOffset[I] << ",\n";
-  O << "  };\n";
-
-  O << "  static const " << StmtType << " " << TargetName
-    << "OverflowProgram[] = {\n"
-    << "    0, 1,\n"; // sentinel + NDEBUG return guard
-  for (const InstructionGroup &G : Groups)
-    for (const AsmWriterOperand &Op : G.FirstInst.Operands)
-      O << "    " << Commands.lookup(Op.getCode(PassSubtarget)) << ",\n";
-  O << "  };\n";
-
-  O << "  for (" << OffsetType << " Idx = " << TargetName
-    << "OpcodeToOffset[MI->getOpcode()];; ++Idx) {\n"
-    << "    switch (" << TargetName << "OverflowProgram[Idx]) {\n"
-    << "    default: llvm_unreachable(\"Unexpected bytecode command.\");\n";
-  for (auto &[Stmt, Idx] : Commands) {
-    O << "    case " << Idx << ":\n      " << Stmt << "\n";
-    if (Idx != 1) // index 1 is the reserved "return;" terminator
-      O << "      break;\n";
-  }
-  O << "    }\n  }\n";
-
-  LLVM_DEBUG(dbgs() << "[AsmWriter] " << TargetName << ": " << Groups.size()
-                    << " unique sequences, " << Commands.size()
-                    << " unique statements\n");
-}
-
 static void
 emitRegisterNameString(raw_ostream &O, StringRef AltName,
                        const std::deque<CodeGenRegister> &Registers) {



More information about the llvm-commits mailing list