Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions include/circt/Dialect/Sim/SimUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===- SimUtils.h - Sim utility entry points --------------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This header file defines utility functions for the Sim dialect.
//
//===----------------------------------------------------------------------===//

#ifndef CIRCT_DIALECT_SIM_SIMUTILS_H
#define CIRCT_DIALECT_SIM_SIMUTILS_H

#include "circt/Dialect/Sim/SimOps.h"

namespace circt {
namespace sim {

/// Erase a print op and cascade-delete dead, side-effect-free producer ops in
/// its reachable dependency graph.
///
/// Precondition: the reachable deletable producer graph is a DAG.
/// In a DAG, this utility fully removes all deletable ops that become dead.
///
/// If the graph is not a DAG, the behavior is still defined: any strongly
/// connected component in that graph cannot be fully cleaned up by this local
/// dead-use criterion and may remain after erasing the root print.
///
/// Note: cyclic `sim.fmt.concat` dependencies are illegal IR by Sim dialect
/// invariants; callers should run this helper on verifier-clean IR.
void cascadeErasePrint(PrintFormattedOp op, mlir::RewriterBase &rewriter);
void cascadeErasePrint(PrintFormattedProcOp op, mlir::RewriterBase &rewriter);

/// TODO: Add explicit cycle detection helper if callers need local validation.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a helper to detect cycle. But it's not quite related to the topic of this PR. So I think I would implement it in a follow-up PR.


} // namespace sim
} // namespace circt

#endif // CIRCT_DIALECT_SIM_SIMUTILS_H
1 change: 1 addition & 0 deletions lib/Dialect/Sim/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_circt_dialect_library(CIRCTSim
SimDialect.cpp
SimOps.cpp
SimTypes.cpp
SimUtils.cpp

ADDITIONAL_HEADER_DIRS
${CIRCT_MAIN_INCLUDE_DIR}/circt/Dialect/Sim
Expand Down
71 changes: 71 additions & 0 deletions lib/Dialect/Sim/SimUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===- SimUtils.cpp - Sim utility entry points ------------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file implements utility functions for the Sim dialect.
//
//===----------------------------------------------------------------------===//

#include "circt/Dialect/Sim/SimUtils.h"
#include "circt/Dialect/Sim/SimOps.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include <queue>

namespace circt {
namespace sim {

// Returns true for producer ops that are safe to remove during cascading erase:
// they are side-effect-free formatting/file-handle construction nodes whose
// only relevant liveness is whether their results are still used.
static bool isDeleteCascadable(Operation *op) {
return isa<FormatLiteralOp, FormatHexOp, FormatOctOp, FormatBinOp,
FormatScientificOp, FormatFloatOp, FormatGeneralOp, FormatDecOp,
FormatCharOp, FormatHierPathOp, FormatStringConcatOp, GetFileOp>(
op);
}
Comment on lines +25 to +30
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be more appropriate to check this using a trait, as it would likely offer better maintainability. I'm wondering whether to add a trait to explicitly express this, or simply use Pure.


template <typename PrintOpTy>
static void cascadeErasePrintImpl(PrintOpTy op, mlir::RewriterBase &rewriter) {
auto *root = op.getOperation();
llvm::DenseSet<Operation *> scheduled;
std::queue<Operation *> toErase;
toErase.push(root);
scheduled.insert(root);
while (!toErase.empty()) {
auto *currentOp = toErase.front();
toErase.pop();

llvm::DenseSet<Operation *> seenProducers;
for (auto operand : currentOp->getOperands()) {
if (auto *definingOp = operand.getDefiningOp();
definingOp && isDeleteCascadable(definingOp) &&
seenProducers.insert(definingOp).second) {
bool allUsesFromCurrent =
llvm::all_of(definingOp->getResults(), [&](Value result) {
return llvm::all_of(result.getUsers(), [&](Operation *user) {
return user == currentOp;
});
});
if (allUsesFromCurrent && scheduled.insert(definingOp).second)
toErase.push(definingOp);
}
}
rewriter.eraseOp(currentOp);
}
}

void cascadeErasePrint(PrintFormattedOp op, mlir::RewriterBase &rewriter) {
cascadeErasePrintImpl(op, rewriter);
}

void cascadeErasePrint(PrintFormattedProcOp op, mlir::RewriterBase &rewriter) {
cascadeErasePrintImpl(op, rewriter);
}

} // namespace sim
} // namespace circt
1 change: 1 addition & 0 deletions unittests/Dialect/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ add_subdirectory(LLHD)
add_subdirectory(OM)
add_subdirectory(RTG)
add_subdirectory(RTGTest)
add_subdirectory(Sim)
add_subdirectory(Synth)
11 changes: 11 additions & 0 deletions unittests/Dialect/Sim/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
add_circt_unittest(CIRCTSimTests
CascadeDeleteTest.cpp
)

target_link_libraries(CIRCTSimTests
PRIVATE
CIRCTHW
CIRCTSim
MLIRIR
MLIRParser
)
209 changes: 209 additions & 0 deletions unittests/Dialect/Sim/CascadeDeleteTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#include "circt/Dialect/HW/HWDialect.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/Seq/SeqDialect.h"
#include "circt/Dialect/Sim/SimDialect.h"
#include "circt/Dialect/Sim/SimOps.h"
#include "circt/Dialect/Sim/SimUtils.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Parser/Parser.h"
#include "gtest/gtest.h"

using namespace mlir;
using namespace circt;

namespace {

const char *irSimpleChain = R"MLIR(
module {
hw.module @test(in %clk : !seq.clock, in %cond : i1) {
%lit = sim.fmt.literal "hello"
%fmt = sim.fmt.concat (%lit)
sim.print %fmt on %clk if %cond
hw.output
}
}
)MLIR";

const char *irSharedInput = R"MLIR(
module {
hw.module @test(in %clk : !seq.clock, in %cond : i1) {
%lit = sim.fmt.literal "shared"
%fmt0 = sim.fmt.concat (%lit, %lit)
%fmt1 = sim.fmt.concat (%lit)
sim.print %fmt0 on %clk if %cond
sim.print %fmt1 on %clk if %cond
hw.output
}
}
)MLIR";

const char *irConditionFromConstant = R"MLIR(
module {
hw.module @test(in %clk : !seq.clock) {
%cond = hw.constant true
%lit = sim.fmt.literal "hello"
%fmt = sim.fmt.concat (%lit)
sim.print %fmt on %clk if %cond
hw.output
}
}
)MLIR";

const char *irDuplicateProducerUse = R"MLIR(
module {
hw.module @test(in %clk : !seq.clock, in %cond : i1) {
%lit = sim.fmt.literal "dup"
%fmt = sim.fmt.concat (%lit, %lit)
sim.print %fmt on %clk if %cond
hw.output
}
}
)MLIR";

const char *irProcPrintGetFile = R"MLIR(
module {
hw.module @test(in %trigger : i1) {
hw.triggered posedge %trigger {
%msg = sim.fmt.literal "hello"
%prefix = sim.fmt.literal "out_"
%suffix = sim.fmt.literal ".log"
%fname = sim.fmt.concat (%prefix, %suffix)
%file = sim.get_file %fname
sim.proc.print %msg to %file
}
hw.output
}
}
)MLIR";

class SimCascadeDeleteTest : public ::testing::Test {
protected:
MLIRContext context;

void SetUp() override {
context.loadDialect<hw::HWDialect>();
context.loadDialect<seq::SeqDialect>();
context.loadDialect<sim::SimDialect>();
}

OwningOpRef<ModuleOp> parseTestModule(const char *source) {
auto module = parseSourceString<ModuleOp>(source, &context);
EXPECT_TRUE(module);
return module;
}

static SmallVector<sim::PrintFormattedOp> collectPrints(ModuleOp module) {
SmallVector<sim::PrintFormattedOp> prints;
module.walk([&](sim::PrintFormattedOp op) { prints.push_back(op); });
return prints;
}

static sim::PrintFormattedOp findSinglePrint(ModuleOp module) {
auto prints = collectPrints(module);
EXPECT_EQ(prints.size(), 1u);
return prints.front();
}

static sim::PrintFormattedProcOp findSingleProcPrint(ModuleOp module) {
sim::PrintFormattedProcOp result;
module.walk([&](sim::PrintFormattedProcOp op) {
EXPECT_FALSE(result);
result = op;
});
EXPECT_TRUE(result);
return result;
}

template <typename OpTy>
static unsigned countOps(Operation *root) {
unsigned count = 0;
root->walk([&](OpTy) { ++count; });
return count;
}

void erasePrint(sim::PrintFormattedOp printOp) {
IRRewriter rewriter(&context);
sim::cascadeErasePrint(printOp, rewriter);
}

void erasePrint(sim::PrintFormattedProcOp printOp) {
IRRewriter rewriter(&context);
sim::cascadeErasePrint(printOp, rewriter);
}
};

TEST_F(SimCascadeDeleteTest, SimpleChain) {
auto module = parseTestModule(irSimpleChain);
ASSERT_TRUE(module);

auto printOp = findSinglePrint(module.get());
ASSERT_TRUE(printOp);

erasePrint(printOp);

ASSERT_EQ(countOps<sim::PrintFormattedOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 0);
}

TEST_F(SimCascadeDeleteTest, SharedInputStaysAlive) {
auto module = parseTestModule(irSharedInput);
ASSERT_TRUE(module);

auto prints = collectPrints(module.get());
ASSERT_EQ(prints.size(), 2u);

erasePrint(prints.front());

ASSERT_EQ(countOps<sim::PrintFormattedOp>(module.get()), 1);
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 1);
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 1);
}

TEST_F(SimCascadeDeleteTest, KeepsNonCascadableConditionProducer) {
auto module = parseTestModule(irConditionFromConstant);
ASSERT_TRUE(module);

auto printOp = findSinglePrint(module.get());
ASSERT_TRUE(printOp);

erasePrint(printOp);

ASSERT_EQ(countOps<sim::PrintFormattedOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 0);
ASSERT_EQ(countOps<hw::ConstantOp>(module.get()), 1);
}

TEST_F(SimCascadeDeleteTest, DuplicateProducerUseBySingleConsumer) {
auto module = parseTestModule(irDuplicateProducerUse);
ASSERT_TRUE(module);

auto printOp = findSinglePrint(module.get());
ASSERT_TRUE(printOp);

erasePrint(printOp);

ASSERT_EQ(countOps<sim::PrintFormattedOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 0);
}

TEST_F(SimCascadeDeleteTest, ProcPrintAlsoCascadesGetFileChain) {
auto module = parseTestModule(irProcPrintGetFile);
ASSERT_TRUE(module);

auto printOp = findSingleProcPrint(module.get());
ASSERT_TRUE(printOp);

erasePrint(printOp);

ASSERT_EQ(countOps<sim::PrintFormattedProcOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::GetFileOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::FormatStringConcatOp>(module.get()), 0);
ASSERT_EQ(countOps<sim::FormatLiteralOp>(module.get()), 0);
}

} // namespace
Loading