[llvm] [IR] Speed up printing instruction sequences (PR #221460)

Yaxun Liu via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 5 20:54:06 PDT 2026


https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/221460

>From 31f36cc037e2d8442dac879bf9c16f391b445666 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Sat, 15 Aug 2026 00:43:35 -0400
Subject: [PATCH] [IR] Speed up printing instruction sequences

Printing instructions one at a time repeatedly rebuilds assembly-writer
state and sends many small writes to the destination stream.

Add a function-scoped instruction printer that reuses one assembly
writer. Collect each instruction in a small reusable buffer before
forwarding it. This preserves interleaved output while reducing write
overhead. Use the printer in CFGPrinter.
---
 .../llvm/IR/FunctionInstructionPrinter.h      | 47 +++++++++++++++++
 llvm/lib/Analysis/CFGPrinter.cpp              |  5 +-
 llvm/lib/IR/AsmWriter.cpp                     | 45 +++++++++++++++++
 llvm/unittests/IR/AsmWriterTest.cpp           | 50 +++++++++++++++++++
 4 files changed, 146 insertions(+), 1 deletion(-)
 create mode 100644 llvm/include/llvm/IR/FunctionInstructionPrinter.h

diff --git a/llvm/include/llvm/IR/FunctionInstructionPrinter.h b/llvm/include/llvm/IR/FunctionInstructionPrinter.h
new file mode 100644
index 0000000000000..4aaba8324520f
--- /dev/null
+++ b/llvm/include/llvm/IR/FunctionInstructionPrinter.h
@@ -0,0 +1,47 @@
+//===- llvm/IR/FunctionInstructionPrinter.h ---------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_IR_FUNCTIONINSTRUCTIONPRINTER_H
+#define LLVM_IR_FUNCTIONINSTRUCTIONPRINTER_H
+
+#include "llvm/Support/Compiler.h"
+#include <memory>
+
+namespace llvm {
+
+class Function;
+class Instruction;
+class ModuleSlotTracker;
+class raw_ostream;
+
+/// Print multiple instructions from one function without repeatedly creating
+/// the underlying assembly writer.
+///
+/// OS, MST, and F must remain valid for this object's lifetime. The function's
+/// IR must not be modified, and MST must not incorporate another function while
+/// the printer is in use. Each instruction is forwarded to OS before
+/// printInstruction returns, so callers can safely interleave other output.
+class LLVM_ABI FunctionInstructionPrinter {
+  struct Impl;
+  std::unique_ptr<Impl> P;
+
+public:
+  FunctionInstructionPrinter(raw_ostream &OS, ModuleSlotTracker &MST,
+                             const Function &F, bool IsForDebug = false);
+  ~FunctionInstructionPrinter();
+
+  FunctionInstructionPrinter(const FunctionInstructionPrinter &) = delete;
+  FunctionInstructionPrinter &
+  operator=(const FunctionInstructionPrinter &) = delete;
+
+  void printInstruction(const Instruction &I);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_IR_FUNCTIONINSTRUCTIONPRINTER_H
diff --git a/llvm/lib/Analysis/CFGPrinter.cpp b/llvm/lib/Analysis/CFGPrinter.cpp
index 39108a906f081..f555b1470041d 100644
--- a/llvm/lib/Analysis/CFGPrinter.cpp
+++ b/llvm/lib/Analysis/CFGPrinter.cpp
@@ -19,6 +19,7 @@
 
 #include "llvm/Analysis/CFGPrinter.h"
 #include "llvm/ADT/PostOrderIterator.h"
+#include "llvm/IR/FunctionInstructionPrinter.h"
 #include "llvm/IR/ModuleSlotTracker.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/FileSystem.h"
@@ -255,8 +256,10 @@ std::string DOTGraphTraits<DOTFuncInfo *>::getCompleteNodeLabel(
             Node.printAsOperand(OS, false, *MST);
             OS << ":\n";
 
+            FunctionInstructionPrinter InstructionPrinter(OS, *MST,
+                                                          *Node.getParent());
             for (const Instruction &Inst : Node) {
-              Inst.print(OS, *MST, /* IsForDebug */ false);
+              InstructionPrinter.printInstruction(Inst);
               OS << '\n';
             }
           }),
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 8e57bff1d36c9..a348bbb45514f 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -44,6 +44,7 @@
 #include "llvm/IR/DebugProgramInstruction.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/Function.h"
+#include "llvm/IR/FunctionInstructionPrinter.h"
 #include "llvm/IR/GlobalAlias.h"
 #include "llvm/IR/GlobalIFunc.h"
 #include "llvm/IR/GlobalObject.h"
@@ -5390,6 +5391,50 @@ void Value::print(raw_ostream &ROS, ModuleSlotTracker &MST,
   }
 }
 
+struct FunctionInstructionPrinter::Impl {
+  const Function &F;
+  raw_ostream &Output;
+  // Collect the assembly writer's small writes before forwarding an
+  // instruction.
+  SmallString<1024> Buffer;
+  raw_svector_ostream BufferOS;
+  std::unique_ptr<SlotTracker> EmptySlotTable;
+  formatted_raw_ostream FormattedOS;
+  std::unique_ptr<AssemblyWriter> Writer;
+
+  Impl(raw_ostream &ROS, ModuleSlotTracker &MST, const Function &F,
+       bool IsForDebug)
+      : F(F), Output(ROS), BufferOS(Buffer), FormattedOS(BufferOS) {
+    FormattedOS.SetBufferSize(Buffer.capacity());
+    MST.incorporateFunction(F);
+    SlotTracker *SlotTable = MST.getMachine();
+    if (!SlotTable) {
+      EmptySlotTable =
+          std::make_unique<SlotTracker>(static_cast<const Module *>(nullptr));
+      SlotTable = EmptySlotTable.get();
+    }
+    Writer = std::make_unique<AssemblyWriter>(
+        FormattedOS, *SlotTable, F.getParent(), nullptr, IsForDebug);
+  }
+};
+
+FunctionInstructionPrinter::FunctionInstructionPrinter(raw_ostream &OS,
+                                                       ModuleSlotTracker &MST,
+                                                       const Function &F,
+                                                       bool IsForDebug)
+    : P(std::make_unique<Impl>(OS, MST, F, IsForDebug)) {}
+
+FunctionInstructionPrinter::~FunctionInstructionPrinter() = default;
+
+void FunctionInstructionPrinter::printInstruction(const Instruction &I) {
+  assert(I.getFunction() == &P->F &&
+         "instruction must belong to the configured function");
+  P->Writer->printInstruction(I);
+  P->FormattedOS.flush();
+  P->Output.write(P->Buffer.data(), P->Buffer.size());
+  P->Buffer.clear();
+}
+
 /// Print without a type, skipping the TypePrinting object.
 ///
 /// \return \c true iff printing was successful.
diff --git a/llvm/unittests/IR/AsmWriterTest.cpp b/llvm/unittests/IR/AsmWriterTest.cpp
index 6c04309af810e..283af85e57ba6 100644
--- a/llvm/unittests/IR/AsmWriterTest.cpp
+++ b/llvm/unittests/IR/AsmWriterTest.cpp
@@ -9,10 +9,13 @@
 #include "llvm/BinaryFormat/Dwarf.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/Function.h"
+#include "llvm/IR/FunctionInstructionPrinter.h"
 #include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstIterator.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/MDBuilder.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/ModuleSlotTracker.h"
 #include "llvm/Support/SourceMgr.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -227,4 +230,51 @@ TEST(AsmWriterTest, PrintNullOperandBundle) {
   Invoke->print(OS);
   EXPECT_THAT(S, HasSubstr("<null operand bundle!>"));
 }
+
+TEST(AsmWriterTest, FunctionInstructionPrinterMatchesInstructionPrint) {
+  LLVMContext Ctx;
+  SMDiagnostic Err;
+  std::unique_ptr<Module> M = parseAssemblyString(R"(
+    declare i32 @callee(i32)
+
+    define i32 @test(i32 %arg, ptr %ptr) {
+    entry:
+      %0 = add nsw i32 %arg, 1, !annotation !0
+      %named = call i32 @callee(i32 %0) [ "tag"(i32 %arg) ], !prof !1
+      store atomic i32 %named, ptr %ptr release, align 4, !annotation !0
+      ret i32 %named
+    }
+
+    !0 = !{!"attached metadata"}
+    !1 = !{!"branch_weights", i32 10}
+  )",
+                                                  Err, Ctx);
+  ASSERT_TRUE(M);
+  Function &F = *M->getFunction("test");
+  // Exercise an instruction that exceeds the printer's internal buffer.
+  F.front().front().setName(std::string(2048, 'x'));
+
+  for (bool IsForDebug : {false, true}) {
+    std::string Expected;
+    raw_string_ostream ExpectedOS(Expected);
+    ModuleSlotTracker ExpectedMST(M.get());
+    ExpectedOS << "before:";
+    for (const Instruction &I : instructions(F)) {
+      I.print(ExpectedOS, ExpectedMST, IsForDebug);
+      ExpectedOS << ":after\n";
+    }
+
+    std::string Actual;
+    raw_string_ostream ActualOS(Actual);
+    ModuleSlotTracker ActualMST(M.get());
+    FunctionInstructionPrinter Printer(ActualOS, ActualMST, F, IsForDebug);
+    ActualOS << "before:";
+    for (const Instruction &I : instructions(F)) {
+      Printer.printInstruction(I);
+      ActualOS << ":after\n";
+    }
+
+    EXPECT_EQ(Expected, Actual);
+  }
+}
 }



More information about the llvm-commits mailing list