[llvm] [TableGen] Make DFA packetizer resource numbering deterministic (PR #207203)

Michal Rostecki via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 2 08:00:26 PDT 2026


https://github.com/vadorovsky created https://github.com/llvm/llvm-project/pull/207203

`DFAPacketizerEmitter` grouped packetizer namespaces in a `StringMap`, so emission order depended on bucket order.

It also reassigned functional unit bits every time a FU name was seen, so duplicate FU names appearing at different positions in different itinerary lists could overwrite earlier assignments and collide with other resources.

This made generated packetizer tables non-reproducible for R600. In AMDGPU's R600 itineraries, `ALU_NULL` appears in both VLIW5 and VLIW4 models at different positions; depending on iteration order it could be assigned either its own bit or collide with `TRANS`.

Fix the emission order by using `std::map`. Make sure that resource bits are assigned once per FU name.

Fixes: https://github.com/llvm/llvm-project/issues/206904
Assisted-by: qwen-code + llama.cpp + hf.co/unsloth/Qwen3.6-35B-A3B-GGUF:UD-Q4_K_M

>From 7f2b97d5ca059de5a5dc772eafaadc380e0cde3e Mon Sep 17 00:00:00 2001
From: vad <vad.sol at proton.me>
Date: Fri, 26 Jun 2026 11:01:58 +0200
Subject: [PATCH 1/2] [TableGen] Make DFA packetizer resource numbering
 deterministic

`DFAPacketizerEmitter` grouped packetizer namespaces in a `StringMap`,
so emission order depended on bucket order.

It also reassigned functional unit bits every time a FU name was seen,
so duplicate FU names appearing at different positions in different
itinerary lists could overwrite earlier assignments and collide with
other resources.

This made generated packetizer tables non-reproducible for R600. In
AMDGPU's R600 itineraries, `ALU_NULL` appears in both VLIW5 and VLIW4
models at different positions; depending on iteration order it could be
assigned either its own bit or collide with `TRANS`.

Fix the emission order by using `std::map`. Make sure that resource bits
are assigned once per FU name.

Fixes: #206904
Assisted-by: qwen-code + llama.cpp + hf.co/unsloth/Qwen3.6-35B-A3B-GGUF:UD-Q4_K_M
---
 llvm/utils/TableGen/DFAPacketizerEmitter.cpp | 24 ++++++++++++--------
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/llvm/utils/TableGen/DFAPacketizerEmitter.cpp b/llvm/utils/TableGen/DFAPacketizerEmitter.cpp
index 334aeef70ff51..0421cc8622dbc 100644
--- a/llvm/utils/TableGen/DFAPacketizerEmitter.cpp
+++ b/llvm/utils/TableGen/DFAPacketizerEmitter.cpp
@@ -18,7 +18,6 @@
 #include "Common/CodeGenTarget.h"
 #include "DFAEmitter.h"
 #include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/StringMap.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TableGen/Record.h"
@@ -118,14 +117,20 @@ int DFAPacketizerEmitter::collectAllFuncUnits(
     // Convert macros to bits for each stage.
     unsigned numFUs = FUs.size();
     for (unsigned j = 0; j < numFUs; ++j) {
-      assert((j < DFA_MAX_RESOURCES) &&
-             "Exceeded maximum number of representable resources");
-      uint64_t FuncResources = 1ULL << j;
-      FUNameToBitsMap[FUs[j]->getName().str()] = FuncResources;
+      std::string FuncName = FUs[j]->getName().str();
+      auto It = FUNameToBitsMap.find(FuncName);
+      uint64_t FuncResources;
+      if (It != FUNameToBitsMap.end()) {
+        FuncResources = It->second;
+      } else {
+        assert((TotalFUs < DFA_MAX_RESOURCES) &&
+               "Exceeded maximum number of representable resources");
+        FuncResources = 1ULL << TotalFUs++;
+        FUNameToBitsMap[FuncName] = FuncResources;
+      }
       LLVM_DEBUG(dbgs() << " " << FUs[j]->getName() << ":0x"
                         << Twine::utohexstr(FuncResources));
     }
-    TotalFUs += numFUs;
     LLVM_DEBUG(dbgs() << "\n");
   }
   return TotalFUs;
@@ -217,16 +222,17 @@ void DFAPacketizerEmitter::run(raw_ostream &OS) {
   CodeGenTarget CGT(Records);
   CodeGenSchedModels CGS(Records, CGT);
 
-  StringMap<std::vector<const CodeGenProcModel *>> ItinsByNamespace;
+  std::map<std::string, std::vector<const CodeGenProcModel *>>
+      ItinsByNamespace;
   for (const CodeGenProcModel &ProcModel : CGS.procModels()) {
     if (ProcModel.hasItineraries()) {
       auto NS = ProcModel.ItinsDef->getValueAsString("PacketizerNamespace");
-      ItinsByNamespace[NS].push_back(&ProcModel);
+      ItinsByNamespace[NS.str()].push_back(&ProcModel);
     }
   }
 
   for (auto &KV : ItinsByNamespace)
-    emitForItineraries(OS, KV.second, KV.first().str());
+    emitForItineraries(OS, KV.second, KV.first);
   OS << "} // end namespace llvm\n";
 }
 

>From bb27f0f31e67aab4ccc39ed6efc2817c015443c6 Mon Sep 17 00:00:00 2001
From: vad <vad.sol at proton.me>
Date: Thu, 2 Jul 2026 16:01:51 +0200
Subject: [PATCH 2/2] [TableGen] Test determinism of DFA packetizer FU
 numbering

Extend the DFA packetizer TableGen test with two itinerary lists where
the same functional unit name appears at different positions.

Check that the generated automaton keeps distinct resource masks for the
unique and shared functional units, and includes their combined mask.
This guards against regressing to per itinerary FU index assignment,
where the shared unit could be reassigned and collide with another
resource.

Ref: #206904
Assisted-by: qwen-code + llama.cpp + hf.co/unsloth/Qwen3.6-35B-A3B-GGUF:UD-Q4_K_M
---
 llvm/test/TableGen/DFAPacketizer.td | 63 ++++++++++++++++++++++++++++-
 1 file changed, 61 insertions(+), 2 deletions(-)

diff --git a/llvm/test/TableGen/DFAPacketizer.td b/llvm/test/TableGen/DFAPacketizer.td
index 6237bfbea9a0f..081e71f1bc7b8 100644
--- a/llvm/test/TableGen/DFAPacketizer.td
+++ b/llvm/test/TableGen/DFAPacketizer.td
@@ -2,7 +2,11 @@
 
 include "llvm/Target/Target.td"
 
-def TestTarget : Target;
+def TestTargetInstrInfo : InstrInfo;
+
+def TestTarget : Target {
+  let InstructionSet = TestTargetInstrInfo;
+}
 
 def TestSchedModel : SchedMachineModel {
   let CompleteModel = 0;
@@ -15,6 +19,18 @@ def FU1 : FuncUnit;
 
 def OP0 : InstrItinClass;
 def OP1 : InstrItinClass;
+def UniqueFUClass : InstrItinClass;
+def SharedFUClass : InstrItinClass;
+
+class TestInst<InstrItinClass Itin> : Instruction {
+  let OutOperandList = (outs);
+  let InOperandList = (ins);
+  let AsmString = "";
+  let Itinerary = Itin;
+}
+
+def INST_UNIQUE : TestInst<UniqueFUClass>;
+def INST_SHARED : TestInst<SharedFUClass>;
 
 def Itin {
   list<InstrItinData> ItinList = [
@@ -23,9 +39,32 @@ def Itin {
   ];
 }
 
+def FU_A : FuncUnit;
+def FU_B : FuncUnit;
+def FU_C : FuncUnit;
+def FU_D : FuncUnit;
+def UNIQUE_FU : FuncUnit;
+def SHARED_FU : FuncUnit;
+
+def WideFUList {
+  list<InstrItinData> ItinList = [
+    InstrItinData<UniqueFUClass, [InstrStage<1, [UNIQUE_FU]>]>,
+    InstrItinData<SharedFUClass, [InstrStage<1, [SHARED_FU]>]>
+  ];
+}
+
+def NarrowFUList {
+  list<InstrItinData> ItinList = [
+    InstrItinData<UniqueFUClass, [InstrStage<1, [SHARED_FU]>]>,
+    InstrItinData<SharedFUClass, [InstrStage<1, [SHARED_FU]>]>
+  ];
+}
+
 // CHECK:      int TestTargetGetResourceIndex(unsigned ProcID) {
 // CHECK-NEXT:   static const unsigned TestTargetProcIdToProcResourceIdxTable[][2] = {
-// CHECK-NEXT:     { 2,  1 }, // TestItinerariesModel
+// CHECK-NEXT:     { {{[0-9]+}},  1 }, // TestNarrowItinerariesModel
+// CHECK-NEXT:     { {{[0-9]+}},  2 }, // TestWideItinerariesModel
+// CHECK-NEXT:     { {{[0-9]+}},  3 }, // TestItinerariesModel
 // CHECK-NEXT:   };
 // CHECK-NEXT:   auto It = llvm::lower_bound(TestTargetProcIdToProcResourceIdxTable, ProcID,
 // CHECK-NEXT:       [](const unsigned LHS[], unsigned Val) { return LHS[0] < Val; });
@@ -33,7 +72,27 @@ def Itin {
 // CHECK-NEXT:   return (*It)[1];
 // CHECK-NEXT: }
 
+// Check that duplicate functional unit names keep their first assigned bit.
+// If `SHARED_FU` was reassigned while processing `TestNarrowItineraries`, it
+// would collide with `UNIQUE_FU` and the generated automaton would not contain
+// the distinct 0x10, 0x20, and 0x30 resource masks below.
+// CHECK: const std::array<NfaStatePair, 8> TestTargetTransitionInfo
+// CHECK:   /* 0 */ {0, 16}, {0, 0},
+// CHECK-NEXT:   /* 2 */ {0, 32}, {0, 0},
+// CHECK:   /* 4 */ {16, 48}, {0, 0},
+// CHECK:   /* 6 */ {32, 48}, {0, 0},
+
 // CHECK:  unsigned Index = TestTargetGetResourceIndex(IID->SchedModel.ProcID);
 
 def TestItineraries: ProcessorItineraries<[], [], Itin.ItinList>;
 def TestProcessor2 : Processor<"testprocessor2", TestItineraries, []>;
+
+def TestWideItineraries :
+    ProcessorItineraries<[FU_A, FU_B, FU_C, FU_D, UNIQUE_FU, SHARED_FU],
+                         [], WideFUList.ItinList>;
+def TestWideProcessor : Processor<"test-wide", TestWideItineraries, []>;
+
+def TestNarrowItineraries :
+    ProcessorItineraries<[FU_A, FU_B, FU_C, FU_D, SHARED_FU],
+                         [], NarrowFUList.ItinList>;
+def TestNarrowProcessor : Processor<"test-narrow", TestNarrowItineraries, []>;



More information about the llvm-commits mailing list