From 0b3a9a954bd28525b32cfa4a2c5efec7db820c19 Mon Sep 17 00:00:00 2001 From: mvpant Date: Fri, 15 Aug 2025 12:56:33 +0300 Subject: [PATCH 1/6] initial --- .../LLHD/Transforms/LowerProcesses.cpp | 108 +++++++++++++++++- .../LLHD/Transforms/lower-processes.mlir | 34 ++++++ 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp index d192e18980df..8d01d78f0ae4 100644 --- a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp +++ b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp @@ -287,10 +287,116 @@ struct LowerProcessesPass : public llhd::impl::LowerProcessesPassBase { void runOnOperation() override; }; + +static bool simplifyBlocksArgs(ProcessOp &processOp) { + SmallVector worklist; + for (auto &block : processOp.getBody()) { + auto waitOp = dyn_cast(block.getTerminator()); + if (!waitOp) + continue; + + auto dstOperands = waitOp.getDestOperands(); + if (dstOperands.empty()) + continue; + + if (llvm::none_of(dstOperands, + [](auto operand) { return isa(operand); })) + continue; + + worklist.push_back(&block); + } + + while (!worklist.empty()) { + auto *block = worklist.pop_back_val(); + + DenseMap blockArgsToPrune; + for (auto &blockArg : block->getArguments()) { + SmallPtrSet branchOperands; + for (auto it = block->pred_begin(); it != block->pred_end(); ++it) { + Block *predecessor = *it; + + if (auto branchOp = + dyn_cast(predecessor->getTerminator())) { + SuccessorOperands successorOperands = + branchOp.getSuccessorOperands(it.getSuccessorIndex()); + + if (Value operand = successorOperands[blockArg.getArgNumber()]) + branchOperands.insert(operand); + } else if (auto waitOp = + dyn_cast(predecessor->getTerminator())) { + auto destOperands = waitOp.getDestOperands(); + branchOperands.insert(destOperands[blockArg.getArgNumber()]); + } else { + llvm_unreachable("TODO"); + } + } + + if (branchOperands.size() == 1) { + blockArgsToPrune.insert( + std::make_pair(blockArg, *branchOperands.begin())); + } + } + + if (blockArgsToPrune.empty()) + continue; + + SmallVector argNums; + llvm::BitVector argsToRemove(block->getNumArguments()); + for (auto &[dstOperand, replaceValue] : blockArgsToPrune) { + LLVM_DEBUG(llvm::dbgs() << "replace" << dstOperand << " with " + << replaceValue << "\n"); + + argNums.push_back(dstOperand.getArgNumber()); + argsToRemove.set(dstOperand.getArgNumber()); + } + + // prune all branch ops operands + // replace all block argument uses + // prune block argument list + llvm::sort(argNums); + for (auto it = block->pred_begin(); it != block->pred_end(); ++it) { + Block *predecessor = *it; + + if (auto branchOp = + dyn_cast(predecessor->getTerminator())) { + + SuccessorOperands successorOperands = + branchOp.getSuccessorOperands(it.getSuccessorIndex()); + + const auto argOff = successorOperands.getProducedOperandCount(); + for (const auto argNum : llvm::reverse(argNums)) { + int start = argOff + argNum; + successorOperands.erase(start); + } + } else if (auto waitOp = dyn_cast(predecessor->getTerminator())) { + auto destOperands = waitOp.getDestOperandsMutable(); + for (const auto argNum : llvm::reverse(argNums)) { + destOperands.erase(argNum); + } + } else { + llvm_unreachable("TODO"); + } + } + + for (auto &[dstOperand, replaceValue] : blockArgsToPrune) { + dstOperand.replaceAllUsesWith(replaceValue); + } + + block->eraseArguments(argsToRemove); + + for (auto *successor : block->getSuccessors()) + worklist.push_back(successor); + } + + return true; +} + } // namespace void LowerProcessesPass::runOnOperation() { SmallVector processOps(getOperation().getOps()); - for (auto processOp : processOps) + for (auto processOp : processOps) { + simplifyBlocksArgs(processOp); Lowering(processOp).lower(); + } } diff --git a/test/Dialect/LLHD/Transforms/lower-processes.mlir b/test/Dialect/LLHD/Transforms/lower-processes.mlir index 48b634ba7974..0e09094bb6cc 100644 --- a/test/Dialect/LLHD/Transforms/lower-processes.mlir +++ b/test/Dialect/LLHD/Transforms/lower-processes.mlir @@ -237,3 +237,37 @@ hw.module @SkipIfEntryAndWaitConvergeWithSideEffectingOps(in %a : i42) { llhd.wait yield (%a : i42), (%a : i42), ^bb1 } } + +// CHECK-LABEL: @PruneWaitOperands +hw.module @PruneWaitOperands(in %clock : i1, in %f1 : i2, in %f2 : i3) { + %false = hw.constant false + %c10_i8 = hw.constant 10 : i8 + %c20_i8 = hw.constant 20 : i8 + %c100_i10 = hw.constant 100 : i10 + %c110_i10 = hw.constant 110 : i10 + %true = hw.constant true + // CHECK: llhd.process -> i1 { + // CHECK-NEXT: cf.br ^bb1(%c10_i8, %c100_i10 : i8, i10) + // CHECK-NEXT: ^bb1(%1: i8, %2: i10): + // CHECK-NEXT: llhd.wait yield (%false : i1), (%clock : i1), ^bb2 + // CHECK-NEXT: ^bb2: + // CHECK: cf.cond_br {{.*}}, ^bb3, ^bb1(%c20_i8, %c110_i10 : i8, i10) + // CHECK-NEXT: ^bb3: + // CHECK: cf.cond_br {{.*}}, ^bb1(%c10_i8, %c100_i10 : i8, i10), ^bb1(%c20_i8, %c110_i10 : i8, i10) + // CHECK-NEXT: } + %0 = llhd.process -> i1 { + cf.br ^bb1(%clock, %c10_i8, %f1, %c100_i10, %f2 : i1, i8, i2, i10, i3) + ^bb1(%1: i1, %2: i8, %3: i2, %4: i10, %5: i3): + llhd.wait yield (%false : i1), (%clock : i1), ^bb2(%2, %1, %3, %5, %4 : i8, i1, i2, i3, i10) + ^bb2(%6: i8, %7: i1, %8: i2, %9: i3, %10: i10): + %15 = comb.xor bin %7, %true : i1 + %16 = comb.and bin %15, %clock : i1 + cf.cond_br %16, ^bb3, ^bb1(%clock, %c20_i8, %f1, %c110_i10, %f2 : i1, i8, i2, i10, i3) + ^bb3: + %25 = comb.and %true, %clock : i1 + cf.cond_br %25, + ^bb1(%clock, %c10_i8, %f1, %c100_i10, %f2 : i1, i8, i2, i10, i3), + ^bb1(%clock, %c20_i8, %f1, %c110_i10, %f2 : i1, i8, i2, i10, i3) + } + hw.output +} From a6308d6d446fcadee607bc45fd94c0207e80cfc5 Mon Sep 17 00:00:00 2001 From: mvpant Date: Tue, 19 Aug 2025 17:15:54 +0300 Subject: [PATCH 2/6] cleanup --- .../LLHD/Transforms/LowerProcesses.cpp | 95 ++++++++++--------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp index 8d01d78f0ae4..0e851056d624 100644 --- a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp +++ b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp @@ -288,6 +288,42 @@ struct LowerProcessesPass void runOnOperation() override; }; +static std::optional> +getBlockArgsToPrune(Block &block) { + DenseMap result; + SmallPtrSet branchOperands; + + for (auto &arg : block.getArguments()) { + for (auto it = block.pred_begin(); it != block.pred_end(); ++it) { + Block *predecessor = *it; + if (auto branchOp = + dyn_cast(predecessor->getTerminator())) { + SuccessorOperands successorOperands = + branchOp.getSuccessorOperands(it.getSuccessorIndex()); + + if (Value operand = successorOperands[arg.getArgNumber()]) { + branchOperands.insert(operand); + } else { + return {}; + } + } else if (auto waitOp = dyn_cast(predecessor->getTerminator())) { + auto destOperands = waitOp.getDestOperands(); + branchOperands.insert(destOperands[arg.getArgNumber()]); + } else { + return {}; + } + } + + if (branchOperands.size() == 1) { + result.insert(std::make_pair(arg, *branchOperands.begin())); + } + + branchOperands.clear(); + } + + return result; +} + static bool simplifyBlocksArgs(ProcessOp &processOp) { SmallVector worklist; for (auto &block : processOp.getBody()) { @@ -308,58 +344,23 @@ static bool simplifyBlocksArgs(ProcessOp &processOp) { while (!worklist.empty()) { auto *block = worklist.pop_back_val(); - - DenseMap blockArgsToPrune; - for (auto &blockArg : block->getArguments()) { - SmallPtrSet branchOperands; - for (auto it = block->pred_begin(); it != block->pred_end(); ++it) { - Block *predecessor = *it; - - if (auto branchOp = - dyn_cast(predecessor->getTerminator())) { - SuccessorOperands successorOperands = - branchOp.getSuccessorOperands(it.getSuccessorIndex()); - - if (Value operand = successorOperands[blockArg.getArgNumber()]) - branchOperands.insert(operand); - } else if (auto waitOp = - dyn_cast(predecessor->getTerminator())) { - auto destOperands = waitOp.getDestOperands(); - branchOperands.insert(destOperands[blockArg.getArgNumber()]); - } else { - llvm_unreachable("TODO"); - } - } - - if (branchOperands.size() == 1) { - blockArgsToPrune.insert( - std::make_pair(blockArg, *branchOperands.begin())); - } - } - - if (blockArgsToPrune.empty()) + auto blockArgsToPrune = getBlockArgsToPrune(*block); + if (!blockArgsToPrune.has_value() || blockArgsToPrune->empty()) continue; - SmallVector argNums; - llvm::BitVector argsToRemove(block->getNumArguments()); - for (auto &[dstOperand, replaceValue] : blockArgsToPrune) { - LLVM_DEBUG(llvm::dbgs() << "replace" << dstOperand << " with " - << replaceValue << "\n"); + llvm::BitVector argsToErase(block->getNumArguments()); + for (auto &[blockArg, replaceValue] : *blockArgsToPrune) { + LLVM_DEBUG(llvm::dbgs() + << "replace" << blockArg << " with " << replaceValue << "\n"); - argNums.push_back(dstOperand.getArgNumber()); - argsToRemove.set(dstOperand.getArgNumber()); + argsToErase.set(blockArg.getArgNumber()); } - // prune all branch ops operands - // replace all block argument uses - // prune block argument list - llvm::sort(argNums); + auto argNums = llvm::to_vector(argsToErase.set_bits()); for (auto it = block->pred_begin(); it != block->pred_end(); ++it) { Block *predecessor = *it; - if (auto branchOp = dyn_cast(predecessor->getTerminator())) { - SuccessorOperands successorOperands = branchOp.getSuccessorOperands(it.getSuccessorIndex()); @@ -374,15 +375,15 @@ static bool simplifyBlocksArgs(ProcessOp &processOp) { destOperands.erase(argNum); } } else { - llvm_unreachable("TODO"); + llvm_unreachable("Unexpected predecessor terminator"); } } - for (auto &[dstOperand, replaceValue] : blockArgsToPrune) { - dstOperand.replaceAllUsesWith(replaceValue); + for (auto &[blockArg, replaceValue] : *blockArgsToPrune) { + blockArg.replaceAllUsesWith(replaceValue); } - block->eraseArguments(argsToRemove); + block->eraseArguments(argsToErase); for (auto *successor : block->getSuccessors()) worklist.push_back(successor); From 9e0319b4bc30ab6547a42446131346bf34afec57 Mon Sep 17 00:00:00 2001 From: mvpant Date: Mon, 25 Aug 2025 18:28:24 +0300 Subject: [PATCH 3/6] impl branch interface in wait op, use simplify region, adapt tests --- .../circt/Dialect/LLHD/IR/LLHDStructureOps.td | 1 + lib/Dialect/LLHD/IR/LLHDOps.cpp | 9 ++ .../LLHD/Transforms/LowerProcesses.cpp | 129 ++++-------------- .../LLHD/Transforms/lower-processes.mlir | 45 +++--- 4 files changed, 62 insertions(+), 122 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td index 5caa20177173..e0a0c8d0af88 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td @@ -144,6 +144,7 @@ def WaitOp : LLHDOp<"wait", [ AttrSizedOperandSegments, HasParent<"ProcessOp">, Terminator, + DeclareOpInterfaceMethods ]> { let summary = "Suspend execution of a process"; let description = [{ diff --git a/lib/Dialect/LLHD/IR/LLHDOps.cpp b/lib/Dialect/LLHD/IR/LLHDOps.cpp index d56f5da60fc8..beb7dc3132a6 100644 --- a/lib/Dialect/LLHD/IR/LLHDOps.cpp +++ b/lib/Dialect/LLHD/IR/LLHDOps.cpp @@ -546,6 +546,15 @@ static LogicalResult verifyYieldResults(Operation *op, return success(); } +SuccessorOperands WaitOp::getSuccessorOperands(unsigned index) { + assert(index == 0 && "invalid successor index"); + return SuccessorOperands(getDestOperandsMutable()); +} + +Block *WaitOp::getSuccessorForOperands(ArrayRef) { + return getDest(); +} + LogicalResult WaitOp::verify() { return verifyYieldResults(*this, getYieldOperands()); } diff --git a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp index 0e851056d624..6d63ca188a75 100644 --- a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp +++ b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp @@ -288,108 +288,31 @@ struct LowerProcessesPass void runOnOperation() override; }; -static std::optional> -getBlockArgsToPrune(Block &block) { - DenseMap result; - SmallPtrSet branchOperands; - - for (auto &arg : block.getArguments()) { - for (auto it = block.pred_begin(); it != block.pred_end(); ++it) { - Block *predecessor = *it; - if (auto branchOp = - dyn_cast(predecessor->getTerminator())) { - SuccessorOperands successorOperands = - branchOp.getSuccessorOperands(it.getSuccessorIndex()); - - if (Value operand = successorOperands[arg.getArgNumber()]) { - branchOperands.insert(operand); - } else { - return {}; - } - } else if (auto waitOp = dyn_cast(predecessor->getTerminator())) { - auto destOperands = waitOp.getDestOperands(); - branchOperands.insert(destOperands[arg.getArgNumber()]); - } else { - return {}; - } - } - - if (branchOperands.size() == 1) { - result.insert(std::make_pair(arg, *branchOperands.begin())); - } - - branchOperands.clear(); - } - - return result; -} - -static bool simplifyBlocksArgs(ProcessOp &processOp) { - SmallVector worklist; - for (auto &block : processOp.getBody()) { - auto waitOp = dyn_cast(block.getTerminator()); - if (!waitOp) - continue; - - auto dstOperands = waitOp.getDestOperands(); - if (dstOperands.empty()) - continue; - - if (llvm::none_of(dstOperands, - [](auto operand) { return isa(operand); })) - continue; - - worklist.push_back(&block); - } - - while (!worklist.empty()) { - auto *block = worklist.pop_back_val(); - auto blockArgsToPrune = getBlockArgsToPrune(*block); - if (!blockArgsToPrune.has_value() || blockArgsToPrune->empty()) - continue; - - llvm::BitVector argsToErase(block->getNumArguments()); - for (auto &[blockArg, replaceValue] : *blockArgsToPrune) { - LLVM_DEBUG(llvm::dbgs() - << "replace" << blockArg << " with " << replaceValue << "\n"); - - argsToErase.set(blockArg.getArgNumber()); - } - - auto argNums = llvm::to_vector(argsToErase.set_bits()); - for (auto it = block->pred_begin(); it != block->pred_end(); ++it) { - Block *predecessor = *it; - if (auto branchOp = - dyn_cast(predecessor->getTerminator())) { - SuccessorOperands successorOperands = - branchOp.getSuccessorOperands(it.getSuccessorIndex()); - - const auto argOff = successorOperands.getProducedOperandCount(); - for (const auto argNum : llvm::reverse(argNums)) { - int start = argOff + argNum; - successorOperands.erase(start); - } - } else if (auto waitOp = dyn_cast(predecessor->getTerminator())) { - auto destOperands = waitOp.getDestOperandsMutable(); - for (const auto argNum : llvm::reverse(argNums)) { - destOperands.erase(argNum); - } - } else { - llvm_unreachable("Unexpected predecessor terminator"); - } - } - - for (auto &[blockArg, replaceValue] : *blockArgsToPrune) { - blockArg.replaceAllUsesWith(replaceValue); - } - - block->eraseArguments(argsToErase); - - for (auto *successor : block->getSuccessors()) - worklist.push_back(successor); - } - - return true; +// Perform cleanup on the process operation prior to lowering. +// This can remove redundant operands passed to the successor of llhd.wait. +// +// ^bb0 +// cf.br ^bb1(%clock : i1) +// ^bb1(%1: i1): // pred: ^bb0, ^bb2 +// llhd.wait yield (...), (%clock : i1), ^bb2(%1 : i1) +// ^bb2(%2: i1): // pred: ^bb1 +// cf.cond_br %true, ^bb3, ^bb1(%2 : i1) +// ^bb3: +// +// The above IR can be rewritten as: +// +// ^bb0 +// cf.br ^bb1 +// ^bb1: // pred: ^bb0, ^bb2 +// llhd.wait yield (...), (%clock : i1), ^bb2 +// ^bb2: // pred: ^bb1 +// cf.cond_br %true, ^bb3, ^bb1 +// ^bb3: +// +static void simplifyProcess(ProcessOp &processOp) { + OpBuilder builder(processOp); + IRRewriter rewriter(builder); + (void)simplifyRegions(rewriter, processOp->getRegions()); } } // namespace @@ -397,7 +320,7 @@ static bool simplifyBlocksArgs(ProcessOp &processOp) { void LowerProcessesPass::runOnOperation() { SmallVector processOps(getOperation().getOps()); for (auto processOp : processOps) { - simplifyBlocksArgs(processOp); + simplifyProcess(processOp); Lowering(processOp).lower(); } } diff --git a/test/Dialect/LLHD/Transforms/lower-processes.mlir b/test/Dialect/LLHD/Transforms/lower-processes.mlir index 0e09094bb6cc..11e668282186 100644 --- a/test/Dialect/LLHD/Transforms/lower-processes.mlir +++ b/test/Dialect/LLHD/Transforms/lower-processes.mlir @@ -158,50 +158,57 @@ hw.module @SkipIfNoWaits() { } // CHECK-LABEL: @SkipIfWaitHasDestinationOperands( -hw.module @SkipIfWaitHasDestinationOperands(in %a: i42) { +hw.module @SkipIfWaitHasDestinationOperands(in %a: i42, in %b: i42) { // CHECK: llhd.process - llhd.process { - cf.br ^bb1 - ^bb1: - llhd.wait ^bb2(%a : i42) - ^bb2(%0: i42): - cf.br ^bb1 + // CHECK: llhd.wait yield ({{.*}} : i42), ^bb2({{.*}} : i42) + %0 = llhd.process -> i42 { + cf.br ^bb1(%a : i42) + ^bb1(%1: i42): + %false = hw.constant false + %c100 = hw.constant 100 : i42 + cf.cond_br %false, ^bb2(%1 : i42), ^bb3(%c100 : i42) + ^bb2(%2: i42): + llhd.wait yield (%2 : i42), ^bb2(%2 : i42) + ^bb3(%3: i42): + %4 = comb.add %3, %b : i42 + cf.br ^bb1(%4 : i42) } } // CHECK-LABEL: @SkipIfEntryAndWaitConvergeInWrongSpot( hw.module @SkipIfEntryAndWaitConvergeInWrongSpot(in %a: i42) { // CHECK: llhd.process + %c100 = hw.constant 100 : i42 llhd.process { - cf.br ^bb2 // skip logic after wait + cf.br ^bb2(%c100 : i42) ^bb1: %0 = comb.add %a, %a : i42 - cf.br ^bb2 - ^bb2: - llhd.wait ^bb1 + cf.br ^bb2(%0 : i42) + ^bb2(%1 : i42): + llhd.wait (%1 : i42), ^bb1 } } // CHECK-LABEL: @SkipIfEntryAndWaitConvergeWithDifferentBlockArgs( hw.module @SkipIfEntryAndWaitConvergeWithDifferentBlockArgs(in %a: i42, in %b: i42) { // CHECK: llhd.process - llhd.process { + %0 = llhd.process -> i42 { cf.br ^bb2(%a : i42) ^bb1: cf.br ^bb2(%b : i42) ^bb2(%0: i42): - llhd.wait ^bb1 + llhd.wait yield (%0 : i42), ^bb1 } } // CHECK-LABEL: @SkipIfValueUnobserved( hw.module @SkipIfValueUnobserved(in %a: i42) { // CHECK: llhd.process - llhd.process { + %0 = llhd.process -> i42 { cf.br ^bb1 ^bb1: %0 = comb.add %a, %a : i42 - llhd.wait ^bb1 + llhd.wait yield (%0 : i42), ^bb1 } } @@ -246,19 +253,19 @@ hw.module @PruneWaitOperands(in %clock : i1, in %f1 : i2, in %f2 : i3) { %c100_i10 = hw.constant 100 : i10 %c110_i10 = hw.constant 110 : i10 %true = hw.constant true - // CHECK: llhd.process -> i1 { + // CHECK: llhd.process -> i1, i8, i2, i10, i3 { // CHECK-NEXT: cf.br ^bb1(%c10_i8, %c100_i10 : i8, i10) // CHECK-NEXT: ^bb1(%1: i8, %2: i10): - // CHECK-NEXT: llhd.wait yield (%false : i1), (%clock : i1), ^bb2 + // CHECK-NEXT: llhd.wait yield (%clock, %1, %f1, %2, %f2 : i1, i8, i2, i10, i3), (%clock : i1), ^bb2 // CHECK-NEXT: ^bb2: // CHECK: cf.cond_br {{.*}}, ^bb3, ^bb1(%c20_i8, %c110_i10 : i8, i10) // CHECK-NEXT: ^bb3: // CHECK: cf.cond_br {{.*}}, ^bb1(%c10_i8, %c100_i10 : i8, i10), ^bb1(%c20_i8, %c110_i10 : i8, i10) // CHECK-NEXT: } - %0 = llhd.process -> i1 { + %0:5 = llhd.process -> i1, i8, i2, i10, i3 { cf.br ^bb1(%clock, %c10_i8, %f1, %c100_i10, %f2 : i1, i8, i2, i10, i3) ^bb1(%1: i1, %2: i8, %3: i2, %4: i10, %5: i3): - llhd.wait yield (%false : i1), (%clock : i1), ^bb2(%2, %1, %3, %5, %4 : i8, i1, i2, i3, i10) + llhd.wait yield (%1, %2, %3, %4, %5 : i1, i8, i2, i10, i3), (%clock : i1), ^bb2(%2, %1, %3, %5, %4 : i8, i1, i2, i3, i10) ^bb2(%6: i8, %7: i1, %8: i2, %9: i3, %10: i10): %15 = comb.xor bin %7, %true : i1 %16 = comb.and bin %15, %clock : i1 From f8a617ad59491b23ac5968423693807cb7b7a0bc Mon Sep 17 00:00:00 2001 From: mvpant Date: Tue, 26 Aug 2025 17:20:25 +0300 Subject: [PATCH 4/6] next iteration --- .../circt/Dialect/LLHD/IR/LLHDStructureOps.td | 3 +- lib/Dialect/LLHD/IR/LLHDOps.cpp | 9 -- .../LLHD/Transforms/LowerProcesses.cpp | 123 +++++++++++++++--- 3 files changed, 103 insertions(+), 32 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td index e0a0c8d0af88..e1edf8b0792c 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td @@ -143,8 +143,7 @@ def CombinationalOp : LLHDOp<"combinational", [ def WaitOp : LLHDOp<"wait", [ AttrSizedOperandSegments, HasParent<"ProcessOp">, - Terminator, - DeclareOpInterfaceMethods + Terminator ]> { let summary = "Suspend execution of a process"; let description = [{ diff --git a/lib/Dialect/LLHD/IR/LLHDOps.cpp b/lib/Dialect/LLHD/IR/LLHDOps.cpp index beb7dc3132a6..d56f5da60fc8 100644 --- a/lib/Dialect/LLHD/IR/LLHDOps.cpp +++ b/lib/Dialect/LLHD/IR/LLHDOps.cpp @@ -546,15 +546,6 @@ static LogicalResult verifyYieldResults(Operation *op, return success(); } -SuccessorOperands WaitOp::getSuccessorOperands(unsigned index) { - assert(index == 0 && "invalid successor index"); - return SuccessorOperands(getDestOperandsMutable()); -} - -Block *WaitOp::getSuccessorForOperands(ArrayRef) { - return getDest(); -} - LogicalResult WaitOp::verify() { return verifyYieldResults(*this, getYieldOperands()); } diff --git a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp index 6d63ca188a75..aeb08f337a71 100644 --- a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp +++ b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp @@ -288,31 +288,112 @@ struct LowerProcessesPass void runOnOperation() override; }; -// Perform cleanup on the process operation prior to lowering. -// This can remove redundant operands passed to the successor of llhd.wait. -// -// ^bb0 -// cf.br ^bb1(%clock : i1) -// ^bb1(%1: i1): // pred: ^bb0, ^bb2 -// llhd.wait yield (...), (%clock : i1), ^bb2(%1 : i1) -// ^bb2(%2: i1): // pred: ^bb1 -// cf.cond_br %true, ^bb3, ^bb1(%2 : i1) -// ^bb3: -// -// The above IR can be rewritten as: -// -// ^bb0 -// cf.br ^bb1 -// ^bb1: // pred: ^bb0, ^bb2 -// llhd.wait yield (...), (%clock : i1), ^bb2 -// ^bb2: // pred: ^bb1 -// cf.cond_br %true, ^bb3, ^bb1 -// ^bb3: -// +static LogicalResult dropRedundantArguments(RewriterBase &rewriter, + Block &block) { + SmallVector argsToErase; + + // Go through the arguments of the block. + for (auto [argIdx, blockOperand] : llvm::enumerate(block.getArguments())) { + bool sameArg = true; + Value commonValue; + + // Go through the block predecessor and flag if they pass to the block + // different values for the same argument. + for (Block::pred_iterator predIt = block.pred_begin(), + predE = block.pred_end(); + predIt != predE; ++predIt) { + Operation *terminator = (*predIt)->getTerminator(); + auto branchOperand = + llvm::TypeSwitch>(terminator) + .Case([&, argIdx = argIdx](BranchOpInterface branchOp) { + auto succIndex = predIt.getSuccessorIndex(); + SuccessorOperands succOperands = + branchOp.getSuccessorOperands(succIndex); + auto branchOperands = succOperands.getForwardedOperands(); + return branchOperands[argIdx]; + }) + .Case([&, argIdx = argIdx](WaitOp waitOp) { + auto destOperands = waitOp.getDestOperands(); + return destOperands[argIdx]; + }) + .Default([](Operation *) { return std::nullopt; }); + + if (!branchOperand.has_value()) { + sameArg = false; + break; + } + + if (!commonValue) { + commonValue = *branchOperand; + continue; + } + + if (*branchOperand != commonValue) { + sameArg = false; + break; + } + } + + // If they are passing the same value, drop the argument. + if (commonValue && sameArg) { + argsToErase.push_back(argIdx); + + // Remove the argument from the block. + rewriter.replaceAllUsesWith(blockOperand, commonValue); + } + } + + // Remove the arguments. + for (size_t argIdx : llvm::reverse(argsToErase)) { + block.eraseArgument(argIdx); + + // Remove the argument from the branch ops. + for (auto predIt = block.pred_begin(), predE = block.pred_end(); + predIt != predE; ++predIt) { + llvm::TypeSwitch((*predIt)->getTerminator()) + .Case([&](BranchOpInterface branchOp) { + auto branch = cast((*predIt)->getTerminator()); + unsigned succIndex = predIt.getSuccessorIndex(); + SuccessorOperands succOperands = + branch.getSuccessorOperands(succIndex); + succOperands.erase(argIdx); + }) + .Case([&](WaitOp waitOp) { + auto destOperands = waitOp.getDestOperandsMutable(); + destOperands.erase(argIdx); + }) + .Default([](Operation *) { + llvm_unreachable("Unexpected predecessor terminator"); + }); + } + } + + return success(!argsToErase.empty()); +} + static void simplifyProcess(ProcessOp &processOp) { OpBuilder builder(processOp); IRRewriter rewriter(builder); (void)simplifyRegions(rewriter, processOp->getRegions()); + + // simplifyRegions does not prune the destination operands of the + // `llhd.wait` operation because it does not implement BranchOpInterface, due + // to its side-effect semantics. Implementing BranchOpInterface could allow + // other optimizations to forward branch operands from the llhd.wait + // operation to the destination block where execution resumes. However, this + // would be invalid, as the SSA value might change across the wait operation. + // Therefore, we manually prune the destination block arguments ourselves. + for (auto &block : processOp.getBody()) { + auto waitOp = dyn_cast(block.getTerminator()); + if (!waitOp) + continue; + + auto dstOperands = waitOp.getDestOperands(); + if (dstOperands.empty()) + continue; + + (void)dropRedundantArguments(rewriter, *waitOp.getDest()); + } } } // namespace From 7c98eb5b4c3795e95b45b792291e8a616a00c7b0 Mon Sep 17 00:00:00 2001 From: mvpant Date: Tue, 26 Aug 2025 17:21:11 +0300 Subject: [PATCH 5/6] fix --- lib/Dialect/LLHD/Transforms/LowerProcesses.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp index aeb08f337a71..823bd3112a33 100644 --- a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp +++ b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp @@ -376,12 +376,12 @@ static void simplifyProcess(ProcessOp &processOp) { IRRewriter rewriter(builder); (void)simplifyRegions(rewriter, processOp->getRegions()); - // simplifyRegions does not prune the destination operands of the - // `llhd.wait` operation because it does not implement BranchOpInterface, due - // to its side-effect semantics. Implementing BranchOpInterface could allow - // other optimizations to forward branch operands from the llhd.wait - // operation to the destination block where execution resumes. However, this - // would be invalid, as the SSA value might change across the wait operation. + // simplifyRegions does not prune the destination operands of the `llhd.wait` + // operation because it does not implement BranchOpInterface, due to its + // side-effect semantics. Implementing BranchOpInterface could allow other + // optimizations to forward branch operands from the `llhd.wait` operation to + // the destination block where execution resumes. However, this would be + // invalid, as the SSA value might change across the wait operation. // Therefore, we manually prune the destination block arguments ourselves. for (auto &block : processOp.getBody()) { auto waitOp = dyn_cast(block.getTerminator()); From 42f1c0a3b97e4a2d9b4acecf060d448173d96b80 Mon Sep 17 00:00:00 2001 From: mvpant Date: Fri, 29 Aug 2025 16:44:28 +0300 Subject: [PATCH 6/6] tweak --- lib/Dialect/LLHD/Transforms/Deseq.cpp | 5 ++ .../LLHD/Transforms/LowerProcesses.cpp | 2 +- .../LLHD/Transforms/lower-processes.mlir | 71 ++++--------------- 3 files changed, 21 insertions(+), 57 deletions(-) diff --git a/lib/Dialect/LLHD/Transforms/Deseq.cpp b/lib/Dialect/LLHD/Transforms/Deseq.cpp index 7b2023fb2fc8..3e74a994d34b 100644 --- a/lib/Dialect/LLHD/Transforms/Deseq.cpp +++ b/lib/Dialect/LLHD/Transforms/Deseq.cpp @@ -505,6 +505,11 @@ TruthTable Deseq::computeBoolean(OpResult value) { return result; } + if (auto instanceOp = dyn_cast(op)) { + // TODO: Should we delve into hw.module itself? + return getUnknownBoolean(); + } + // Otherwise check if the operation depends on any of the triggers. If it // does, create a poison value since we don't really know how the trigger // affects this boolean. If it doesn't, create an unknown value. diff --git a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp index 823bd3112a33..845c67698063 100644 --- a/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp +++ b/lib/Dialect/LLHD/Transforms/LowerProcesses.cpp @@ -401,7 +401,7 @@ static void simplifyProcess(ProcessOp &processOp) { void LowerProcessesPass::runOnOperation() { SmallVector processOps(getOperation().getOps()); for (auto processOp : processOps) { - simplifyProcess(processOp); + // simplifyProcess(processOp); Lowering(processOp).lower(); } } diff --git a/test/Dialect/LLHD/Transforms/lower-processes.mlir b/test/Dialect/LLHD/Transforms/lower-processes.mlir index 11e668282186..48b634ba7974 100644 --- a/test/Dialect/LLHD/Transforms/lower-processes.mlir +++ b/test/Dialect/LLHD/Transforms/lower-processes.mlir @@ -158,57 +158,50 @@ hw.module @SkipIfNoWaits() { } // CHECK-LABEL: @SkipIfWaitHasDestinationOperands( -hw.module @SkipIfWaitHasDestinationOperands(in %a: i42, in %b: i42) { +hw.module @SkipIfWaitHasDestinationOperands(in %a: i42) { // CHECK: llhd.process - // CHECK: llhd.wait yield ({{.*}} : i42), ^bb2({{.*}} : i42) - %0 = llhd.process -> i42 { - cf.br ^bb1(%a : i42) - ^bb1(%1: i42): - %false = hw.constant false - %c100 = hw.constant 100 : i42 - cf.cond_br %false, ^bb2(%1 : i42), ^bb3(%c100 : i42) - ^bb2(%2: i42): - llhd.wait yield (%2 : i42), ^bb2(%2 : i42) - ^bb3(%3: i42): - %4 = comb.add %3, %b : i42 - cf.br ^bb1(%4 : i42) + llhd.process { + cf.br ^bb1 + ^bb1: + llhd.wait ^bb2(%a : i42) + ^bb2(%0: i42): + cf.br ^bb1 } } // CHECK-LABEL: @SkipIfEntryAndWaitConvergeInWrongSpot( hw.module @SkipIfEntryAndWaitConvergeInWrongSpot(in %a: i42) { // CHECK: llhd.process - %c100 = hw.constant 100 : i42 llhd.process { - cf.br ^bb2(%c100 : i42) + cf.br ^bb2 // skip logic after wait ^bb1: %0 = comb.add %a, %a : i42 - cf.br ^bb2(%0 : i42) - ^bb2(%1 : i42): - llhd.wait (%1 : i42), ^bb1 + cf.br ^bb2 + ^bb2: + llhd.wait ^bb1 } } // CHECK-LABEL: @SkipIfEntryAndWaitConvergeWithDifferentBlockArgs( hw.module @SkipIfEntryAndWaitConvergeWithDifferentBlockArgs(in %a: i42, in %b: i42) { // CHECK: llhd.process - %0 = llhd.process -> i42 { + llhd.process { cf.br ^bb2(%a : i42) ^bb1: cf.br ^bb2(%b : i42) ^bb2(%0: i42): - llhd.wait yield (%0 : i42), ^bb1 + llhd.wait ^bb1 } } // CHECK-LABEL: @SkipIfValueUnobserved( hw.module @SkipIfValueUnobserved(in %a: i42) { // CHECK: llhd.process - %0 = llhd.process -> i42 { + llhd.process { cf.br ^bb1 ^bb1: %0 = comb.add %a, %a : i42 - llhd.wait yield (%0 : i42), ^bb1 + llhd.wait ^bb1 } } @@ -244,37 +237,3 @@ hw.module @SkipIfEntryAndWaitConvergeWithSideEffectingOps(in %a : i42) { llhd.wait yield (%a : i42), (%a : i42), ^bb1 } } - -// CHECK-LABEL: @PruneWaitOperands -hw.module @PruneWaitOperands(in %clock : i1, in %f1 : i2, in %f2 : i3) { - %false = hw.constant false - %c10_i8 = hw.constant 10 : i8 - %c20_i8 = hw.constant 20 : i8 - %c100_i10 = hw.constant 100 : i10 - %c110_i10 = hw.constant 110 : i10 - %true = hw.constant true - // CHECK: llhd.process -> i1, i8, i2, i10, i3 { - // CHECK-NEXT: cf.br ^bb1(%c10_i8, %c100_i10 : i8, i10) - // CHECK-NEXT: ^bb1(%1: i8, %2: i10): - // CHECK-NEXT: llhd.wait yield (%clock, %1, %f1, %2, %f2 : i1, i8, i2, i10, i3), (%clock : i1), ^bb2 - // CHECK-NEXT: ^bb2: - // CHECK: cf.cond_br {{.*}}, ^bb3, ^bb1(%c20_i8, %c110_i10 : i8, i10) - // CHECK-NEXT: ^bb3: - // CHECK: cf.cond_br {{.*}}, ^bb1(%c10_i8, %c100_i10 : i8, i10), ^bb1(%c20_i8, %c110_i10 : i8, i10) - // CHECK-NEXT: } - %0:5 = llhd.process -> i1, i8, i2, i10, i3 { - cf.br ^bb1(%clock, %c10_i8, %f1, %c100_i10, %f2 : i1, i8, i2, i10, i3) - ^bb1(%1: i1, %2: i8, %3: i2, %4: i10, %5: i3): - llhd.wait yield (%1, %2, %3, %4, %5 : i1, i8, i2, i10, i3), (%clock : i1), ^bb2(%2, %1, %3, %5, %4 : i8, i1, i2, i3, i10) - ^bb2(%6: i8, %7: i1, %8: i2, %9: i3, %10: i10): - %15 = comb.xor bin %7, %true : i1 - %16 = comb.and bin %15, %clock : i1 - cf.cond_br %16, ^bb3, ^bb1(%clock, %c20_i8, %f1, %c110_i10, %f2 : i1, i8, i2, i10, i3) - ^bb3: - %25 = comb.and %true, %clock : i1 - cf.cond_br %25, - ^bb1(%clock, %c10_i8, %f1, %c100_i10, %f2 : i1, i8, i2, i10, i3), - ^bb1(%clock, %c20_i8, %f1, %c110_i10, %f2 : i1, i8, i2, i10, i3) - } - hw.output -}