[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