Skip to content

Commit b4784a8

Browse files
committed
[Sim] Add cascade erase for print/proc.print format/get_file producer chains
1 parent ed3f87d commit b4784a8

6 files changed

Lines changed: 334 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===- SimUtils.h - Sim utility entry points --------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This header file defines utility functions for the Sim dialect.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef CIRCT_DIALECT_SIM_SIMUTILS_H
14+
#define CIRCT_DIALECT_SIM_SIMUTILS_H
15+
16+
#include "circt/Dialect/Sim/SimOps.h"
17+
18+
namespace circt {
19+
namespace sim {
20+
21+
/// Erase a print op and cascade-delete dead, side-effect-free producer ops in
22+
/// its reachable dependency graph.
23+
///
24+
/// Precondition: the reachable deletable producer graph is a DAG.
25+
/// In a DAG, this utility fully removes all deletable ops that become dead.
26+
///
27+
/// If the graph is not a DAG, the behavior is still defined: any strongly
28+
/// connected component in that graph cannot be fully cleaned up by this local
29+
/// dead-use criterion and may remain after erasing the root print.
30+
///
31+
/// Note: cyclic `sim.fmt.concat` dependencies are illegal IR by Sim dialect
32+
/// invariants; callers should run this helper on verifier-clean IR.
33+
void cascadeErasePrint(PrintFormattedOp op, mlir::RewriterBase &rewriter);
34+
void cascadeErasePrint(PrintFormattedProcOp op, mlir::RewriterBase &rewriter);
35+
36+
/// TODO: Add explicit cycle detection helper if callers need local validation.
37+
38+
} // namespace sim
39+
} // namespace circt
40+
41+
#endif // CIRCT_DIALECT_SIM_SIMUTILS_H

lib/Dialect/Sim/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_circt_dialect_library(CIRCTSim
1313
SimDialect.cpp
1414
SimOps.cpp
1515
SimTypes.cpp
16+
SimUtils.cpp
1617

1718
ADDITIONAL_HEADER_DIRS
1819
${CIRCT_MAIN_INCLUDE_DIR}/circt/Dialect/Sim

lib/Dialect/Sim/SimUtils.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//===- SimUtils.cpp - Sim utility entry points ------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file implements utility functions for the Sim dialect.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "circt/Dialect/Sim/SimUtils.h"
14+
#include "circt/Dialect/Sim/SimOps.h"
15+
#include "llvm/ADT/DenseSet.h"
16+
#include "llvm/ADT/STLExtras.h"
17+
#include <queue>
18+
19+
namespace circt {
20+
namespace sim {
21+
22+
// Returns true for producer ops that are safe to remove during cascading erase:
23+
// they are side-effect-free formatting/file-handle construction nodes whose
24+
// only relevant liveness is whether their results are still used.
25+
static bool isDeleteCascadable(Operation *op) {
26+
return isa<FormatLiteralOp, FormatHexOp, FormatOctOp, FormatBinOp,
27+
FormatScientificOp, FormatFloatOp, FormatGeneralOp, FormatDecOp,
28+
FormatCharOp, FormatHierPathOp, FormatStringConcatOp, GetFileOp>(
29+
op);
30+
}
31+
32+
template <typename PrintOpTy>
33+
static void cascadeErasePrintImpl(PrintOpTy op, mlir::RewriterBase &rewriter) {
34+
auto *root = op.getOperation();
35+
llvm::DenseSet<Operation *> scheduled;
36+
std::queue<Operation *> toErase;
37+
toErase.push(root);
38+
scheduled.insert(root);
39+
while (!toErase.empty()) {
40+
auto *currentOp = toErase.front();
41+
toErase.pop();
42+
43+
llvm::DenseSet<Operation *> seenProducers;
44+
for (auto operand : currentOp->getOperands()) {
45+
if (auto *definingOp = operand.getDefiningOp();
46+
definingOp && isDeleteCascadable(definingOp) &&
47+
seenProducers.insert(definingOp).second) {
48+
bool allUsesFromCurrent = llvm::all_of(
49+
definingOp->getResults(), [&](Value result) {
50+
return llvm::all_of(result.getUsers(), [&](Operation *user) {
51+
return user == currentOp;
52+
});
53+
});
54+
if (allUsesFromCurrent && scheduled.insert(definingOp).second)
55+
toErase.push(definingOp);
56+
}
57+
}
58+
rewriter.eraseOp(currentOp);
59+
}
60+
}
61+
62+
void cascadeErasePrint(PrintFormattedOp op, mlir::RewriterBase &rewriter) {
63+
cascadeErasePrintImpl(op, rewriter);
64+
}
65+
66+
void cascadeErasePrint(PrintFormattedProcOp op, mlir::RewriterBase &rewriter) {
67+
cascadeErasePrintImpl(op, rewriter);
68+
}
69+
70+
} // namespace sim
71+
} // namespace circt

unittests/Dialect/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ add_subdirectory(LLHD)
88
add_subdirectory(OM)
99
add_subdirectory(RTG)
1010
add_subdirectory(RTGTest)
11+
add_subdirectory(Sim)
1112
add_subdirectory(Synth)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
add_circt_unittest(CIRCTSimTests
2+
CascadeDeleteTest.cpp
3+
)
4+
5+
target_link_libraries(CIRCTSimTests
6+
PRIVATE
7+
CIRCTHW
8+
CIRCTSim
9+
MLIRIR
10+
MLIRParser
11+
)
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#include "circt/Dialect/HW/HWDialect.h"
2+
#include "circt/Dialect/HW/HWOps.h"
3+
#include "circt/Dialect/Seq/SeqDialect.h"
4+
#include "circt/Dialect/Sim/SimDialect.h"
5+
#include "circt/Dialect/Sim/SimOps.h"
6+
#include "circt/Dialect/Sim/SimUtils.h"
7+
#include "mlir/IR/BuiltinOps.h"
8+
#include "mlir/IR/MLIRContext.h"
9+
#include "mlir/IR/PatternMatch.h"
10+
#include "mlir/Parser/Parser.h"
11+
#include "gtest/gtest.h"
12+
13+
using namespace mlir;
14+
using namespace circt;
15+
16+
namespace {
17+
18+
const char *irSimpleChain = R"MLIR(
19+
module {
20+
hw.module @test(in %clk : !seq.clock, in %cond : i1) {
21+
%lit = sim.fmt.literal "hello"
22+
%fmt = sim.fmt.concat (%lit)
23+
sim.print %fmt on %clk if %cond
24+
hw.output
25+
}
26+
}
27+
)MLIR";
28+
29+
const char *irSharedInput = R"MLIR(
30+
module {
31+
hw.module @test(in %clk : !seq.clock, in %cond : i1) {
32+
%lit = sim.fmt.literal "shared"
33+
%fmt0 = sim.fmt.concat (%lit, %lit)
34+
%fmt1 = sim.fmt.concat (%lit)
35+
sim.print %fmt0 on %clk if %cond
36+
sim.print %fmt1 on %clk if %cond
37+
hw.output
38+
}
39+
}
40+
)MLIR";
41+
42+
const char *irConditionFromConstant = R"MLIR(
43+
module {
44+
hw.module @test(in %clk : !seq.clock) {
45+
%cond = hw.constant true
46+
%lit = sim.fmt.literal "hello"
47+
%fmt = sim.fmt.concat (%lit)
48+
sim.print %fmt on %clk if %cond
49+
hw.output
50+
}
51+
}
52+
)MLIR";
53+
54+
const char *irDuplicateProducerUse = R"MLIR(
55+
module {
56+
hw.module @test(in %clk : !seq.clock, in %cond : i1) {
57+
%lit = sim.fmt.literal "dup"
58+
%fmt = sim.fmt.concat (%lit, %lit)
59+
sim.print %fmt on %clk if %cond
60+
hw.output
61+
}
62+
}
63+
)MLIR";
64+
65+
const char *irProcPrintGetFile = R"MLIR(
66+
module {
67+
hw.module @test(in %trigger : i1) {
68+
hw.triggered posedge %trigger {
69+
%msg = sim.fmt.literal "hello"
70+
%prefix = sim.fmt.literal "out_"
71+
%suffix = sim.fmt.literal ".log"
72+
%fname = sim.fmt.concat (%prefix, %suffix)
73+
%file = sim.get_file %fname
74+
sim.proc.print %msg to %file
75+
}
76+
hw.output
77+
}
78+
}
79+
)MLIR";
80+
81+
class SimCascadeDeleteTest : public ::testing::Test {
82+
protected:
83+
MLIRContext context;
84+
85+
void SetUp() override {
86+
context.loadDialect<hw::HWDialect>();
87+
context.loadDialect<seq::SeqDialect>();
88+
context.loadDialect<sim::SimDialect>();
89+
}
90+
91+
OwningOpRef<ModuleOp> parseTestModule(const char *source) {
92+
auto module = parseSourceString<ModuleOp>(source, &context);
93+
EXPECT_TRUE(module);
94+
return module;
95+
}
96+
97+
static SmallVector<sim::PrintFormattedOp> collectPrints(ModuleOp module) {
98+
SmallVector<sim::PrintFormattedOp> prints;
99+
module.walk([&](sim::PrintFormattedOp op) { prints.push_back(op); });
100+
return prints;
101+
}
102+
103+
static sim::PrintFormattedOp findSinglePrint(ModuleOp module) {
104+
auto prints = collectPrints(module);
105+
EXPECT_EQ(prints.size(), 1u);
106+
return prints.front();
107+
}
108+
109+
static sim::PrintFormattedProcOp findSingleProcPrint(ModuleOp module) {
110+
sim::PrintFormattedProcOp result;
111+
module.walk([&](sim::PrintFormattedProcOp op) {
112+
EXPECT_FALSE(result);
113+
result = op;
114+
});
115+
EXPECT_TRUE(result);
116+
return result;
117+
}
118+
119+
template <typename OpTy>
120+
static unsigned countOps(Operation *root) {
121+
unsigned count = 0;
122+
root->walk([&](OpTy) { ++count; });
123+
return count;
124+
}
125+
126+
void erasePrint(sim::PrintFormattedOp printOp) {
127+
IRRewriter rewriter(&context);
128+
sim::cascadeErasePrint(printOp, rewriter);
129+
}
130+
131+
void erasePrint(sim::PrintFormattedProcOp printOp) {
132+
IRRewriter rewriter(&context);
133+
sim::cascadeErasePrint(printOp, rewriter);
134+
}
135+
};
136+
137+
TEST_F(SimCascadeDeleteTest, SimpleChain) {
138+
auto module = parseTestModule(irSimpleChain);
139+
ASSERT_TRUE(module);
140+
141+
auto printOp = findSinglePrint(module.get());
142+
ASSERT_TRUE(printOp);
143+
144+
erasePrint(printOp);
145+
146+
ASSERT_EQ(countOps<sim::PrintFormattedOp>(module.get()), 0);
147+
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 0);
148+
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 0);
149+
}
150+
151+
TEST_F(SimCascadeDeleteTest, SharedInputStaysAlive) {
152+
auto module = parseTestModule(irSharedInput);
153+
ASSERT_TRUE(module);
154+
155+
auto prints = collectPrints(module.get());
156+
ASSERT_EQ(prints.size(), 2u);
157+
158+
erasePrint(prints.front());
159+
160+
ASSERT_EQ(countOps<sim::PrintFormattedOp>(module.get()), 1);
161+
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 1);
162+
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 1);
163+
}
164+
165+
TEST_F(SimCascadeDeleteTest, KeepsNonCascadableConditionProducer) {
166+
auto module = parseTestModule(irConditionFromConstant);
167+
ASSERT_TRUE(module);
168+
169+
auto printOp = findSinglePrint(module.get());
170+
ASSERT_TRUE(printOp);
171+
172+
erasePrint(printOp);
173+
174+
ASSERT_EQ(countOps<sim::PrintFormattedOp>(module.get()), 0);
175+
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 0);
176+
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 0);
177+
ASSERT_EQ(countOps<hw::ConstantOp>(module.get()), 1);
178+
}
179+
180+
TEST_F(SimCascadeDeleteTest, DuplicateProducerUseBySingleConsumer) {
181+
auto module = parseTestModule(irDuplicateProducerUse);
182+
ASSERT_TRUE(module);
183+
184+
auto printOp = findSinglePrint(module.get());
185+
ASSERT_TRUE(printOp);
186+
187+
erasePrint(printOp);
188+
189+
ASSERT_EQ(countOps<sim::PrintFormattedOp>(module.get()), 0);
190+
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 0);
191+
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 0);
192+
}
193+
194+
TEST_F(SimCascadeDeleteTest, ProcPrintAlsoCascadesGetFileChain) {
195+
auto module = parseTestModule(irProcPrintGetFile);
196+
ASSERT_TRUE(module);
197+
198+
auto printOp = findSingleProcPrint(module.get());
199+
ASSERT_TRUE(printOp);
200+
201+
erasePrint(printOp);
202+
203+
ASSERT_EQ(countOps<sim::PrintFormattedProcOp>(module.get()), 0);
204+
ASSERT_EQ(countOps<sim::GetFileOp>(module.get()), 0);
205+
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 0);
206+
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 0);
207+
}
208+
209+
} // namespace

0 commit comments

Comments
 (0)