diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index 0e903b311bb1..14751b58794b 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -266,19 +266,20 @@ def NetOp : MooreOp<"net", [ `` custom($name) $kind ($assignment^)? attr-dict `:` type($result) }]; + let hasCanonicalizeMethod = true; } -def AssignedVarOp : MooreOp<"assigned_variable", [ +def AssignedVariableOp : MooreOp<"assigned_variable", [ DeclareOpInterfaceMethods, - TypesMatchWith<"initial value and variable types match", - "result", "initial", "cast($_self).getNestedType()"> + SameOperandsAndResultType ]> { - let summary = "The copy of variable must have the initial value"; - let arguments = (ins OptionalAttr:$name, UnpackedType:$initial); - let results = (outs Res:$result); + let summary = "A variable with a unique continuously assigned value"; + let arguments = (ins OptionalAttr:$name, UnpackedType:$input); + let results = (outs UnpackedType:$result); let assemblyFormat = [{ - `` custom($name) $initial attr-dict `:` type($result) + `` custom($name) $input attr-dict `:` type($input) }]; + let hasCanonicalizeMethod = true; } def ReadOp : MooreOp<"read", [ diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 6920c404cd93..20553e17d720 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -289,25 +289,49 @@ VariableOp::handlePromotionComplete(const MemorySlot &slot, Value defaultValue, } LogicalResult VariableOp::canonicalize(VariableOp op, - ::mlir::PatternRewriter &rewriter) { - Value initial; - for (auto *user : op->getUsers()) - if (isa(user) && - (user->getOperand(0) == op.getResult())) { - // Don't canonicalize the multiple continuous assignment to the same - // variable. - if (initial) + PatternRewriter &rewriter) { + // Check if the variable has one unique continuous assignment to it, all other + // uses are reads, and that all uses are in the same block as the variable + // itself. + auto *block = op->getBlock(); + ContinuousAssignOp uniqueAssignOp; + for (auto *user : op->getUsers()) { + // Ensure that all users of the variable are in the same block. + if (user->getBlock() != block) + return failure(); + + // Ensure there is at most one unique continuous assignment to the variable. + if (auto assignOp = dyn_cast(user)) { + if (uniqueAssignOp) return failure(); - initial = user->getOperand(1); + uniqueAssignOp = assignOp; + continue; } - if (initial) { - rewriter.replaceOpWithNewOp(op, op.getType(), - op.getNameAttr(), initial); - return success(); + // Ensure all other users are reads. + if (!isa(user)) + return failure(); } + if (!uniqueAssignOp) + return failure(); - return failure(); + // If the original variable had a name, create an `AssignedVariableOp` as a + // replacement. Otherwise substitute the assigned value directly. + Value assignedValue = uniqueAssignOp.getSrc(); + if (auto name = op.getNameAttr(); name && !name.empty()) + assignedValue = rewriter.create( + op.getLoc(), name, uniqueAssignOp.getSrc()); + + // Remove the assign op and replace all reads with the new assigned var op. + rewriter.eraseOp(uniqueAssignOp); + for (auto *user : llvm::make_early_inc_range(op->getUsers())) { + auto readOp = cast(user); + rewriter.replaceOp(readOp, assignedValue); + } + + // Remove the original variable. + rewriter.eraseOp(op); + return success(); } SmallVector VariableOp::getDestructurableSlots() { @@ -371,15 +395,118 @@ void NetOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { setNameFn(getResult(), *getName()); } +LogicalResult NetOp::canonicalize(NetOp op, PatternRewriter &rewriter) { + bool modified = false; + + // Check if the net has one unique continuous assignment to it, and + // additionally if all other users are reads. + auto *block = op->getBlock(); + ContinuousAssignOp uniqueAssignOp; + bool allUsesAreReads = true; + for (auto *user : op->getUsers()) { + // Ensure that all users of the net are in the same block. + if (user->getBlock() != block) + return failure(); + + // Ensure there is at most one unique continuous assignment to the net. + if (auto assignOp = dyn_cast(user)) { + if (uniqueAssignOp) + return failure(); + uniqueAssignOp = assignOp; + continue; + } + + // Ensure all other users are reads. + if (!isa(user)) + allUsesAreReads = false; + } + + // If there was one unique assignment, and the `NetOp` does not yet have an + // assigned value set, fold the assignment into the net. + if (uniqueAssignOp && !op.getAssignment()) { + rewriter.modifyOpInPlace( + op, [&] { op.getAssignmentMutable().assign(uniqueAssignOp.getSrc()); }); + rewriter.eraseOp(uniqueAssignOp); + modified = true; + uniqueAssignOp = {}; + } + + // If all users of the net op are reads, and any potential unique assignment + // has been folded into the net op itself, directly replace the reads with the + // net's assigned value. + if (!uniqueAssignOp && allUsesAreReads && op.getAssignment()) { + // If the original net had a name, create an `AssignedVariableOp` as a + // replacement. Otherwise substitute the assigned value directly. + auto assignedValue = op.getAssignment(); + if (auto name = op.getNameAttr(); name && !name.empty()) + assignedValue = + rewriter.create(op.getLoc(), name, assignedValue); + + // Replace all reads with the new assigned var op and remove the original + // net op. + for (auto *user : llvm::make_early_inc_range(op->getUsers())) { + auto readOp = cast(user); + rewriter.replaceOp(readOp, assignedValue); + } + rewriter.eraseOp(op); + modified = true; + } + + return success(modified); +} + //===----------------------------------------------------------------------===// -// AssignedVarOp +// AssignedVariableOp //===----------------------------------------------------------------------===// -void AssignedVarOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { +void AssignedVariableOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { if (getName() && !getName()->empty()) setNameFn(getResult(), *getName()); } +LogicalResult AssignedVariableOp::canonicalize(AssignedVariableOp op, + PatternRewriter &rewriter) { + // Eliminate chained variables with the same name. + // var(name, var(name, x)) -> var(name, x) + if (auto otherOp = op.getInput().getDefiningOp()) { + if (otherOp.getNameAttr() == op.getNameAttr()) { + rewriter.replaceOp(op, otherOp); + return success(); + } + } + + // Eliminate variables that alias an input port of the same name. + if (auto blockArg = dyn_cast(op.getInput())) { + if (auto moduleOp = + dyn_cast(blockArg.getOwner()->getParentOp())) { + auto moduleType = moduleOp.getModuleType(); + auto portName = moduleType.getInputNameAttr(blockArg.getArgNumber()); + if (portName == op.getNameAttr()) { + rewriter.replaceOp(op, blockArg); + return success(); + } + } + } + + // Eliminate variables that feed an output port of the same name. + for (auto &use : op->getUses()) { + auto outputOp = dyn_cast(use.getOwner()); + if (!outputOp) + continue; + auto moduleOp = dyn_cast(outputOp.getParentOp()); + if (!moduleOp) + break; + auto moduleType = moduleOp.getModuleType(); + auto portName = moduleType.getOutputNameAttr(use.getOperandNumber()); + if (portName == op.getNameAttr()) { + rewriter.replaceOp(op, op.getInput()); + return success(); + } + } + + return failure(); +} + //===----------------------------------------------------------------------===// // ConstantOp //===----------------------------------------------------------------------===// diff --git a/test/Dialect/Moore/canonicalizers.mlir b/test/Dialect/Moore/canonicalizers.mlir index 872fa7766d00..0104799c0946 100644 --- a/test/Dialect/Moore/canonicalizers.mlir +++ b/test/Dialect/Moore/canonicalizers.mlir @@ -10,32 +10,168 @@ func.func @Casts(%arg0: !moore.i1) -> (!moore.i1, !moore.i1) { return %0, %1 : !moore.i1, !moore.i1 } -// CHECK-LABEL: moore.module @SingleAssign -moore.module @SingleAssign() { +// CHECK-LABEL: moore.module @OptimizeUniquelyAssignedVars +moore.module @OptimizeUniquelyAssignedVars(in %u: !moore.i42, in %v: !moore.i42, in %w: !moore.i42) { + // Unique continuous assignments to variables should remove the `ref` + // indirection and instead directly propagate the assigned value to readers. + // CHECK-NOT: moore.assign // CHECK-NOT: moore.variable - // CHECK: %a = moore.assigned_variable %0 : - %a = moore.variable : - // CHECK: %0 = moore.constant 32 : i32 - %0 = moore.constant 32 : i32 - // CHECK: moore.assign %a, %0 : i32 - moore.assign %a, %0 : i32 - moore.output + // CHECK: %a = moore.assigned_variable %u : i42 + // CHECK: dbg.variable "a", %a : !moore.i42 + moore.assign %a, %u : i42 + %a = moore.variable : + %3 = moore.read %a : + dbg.variable "a", %3 : !moore.i42 + + // Continuous assignments to variables should override the initial value. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.constant 9001 + // CHECK-NOT: moore.variable + // CHECK: %b = moore.assigned_variable %v : i42 + // CHECK: dbg.variable "b", %b : !moore.i42 + moore.assign %b, %v : i42 + %0 = moore.constant 9001 : i42 + %b = moore.variable %0 : + %4 = moore.read %b : + dbg.variable "b", %4 : !moore.i42 + + // Unique continuous assignments to nets should remove the `ref` + // indirection and instead directly propagate the assigned value to readers. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.net wire + // CHECK: %c = moore.assigned_variable %w : i42 + // CHECK: dbg.variable "c", %c : !moore.i42 + moore.assign %c, %w : i42 + %c = moore.net wire : + %5 = moore.read %c : + dbg.variable "c", %5 : !moore.i42 + + // Variables without names should not create an `assigned_variable`. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.variable + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "d", %u : !moore.i42 + moore.assign %1, %u : i42 + %1 = moore.variable : + %6 = moore.read %1 : + dbg.variable "d", %6 : !moore.i42 + + // Nets without names should not create an `assigned_variable`. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.net wire + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "e", %v : !moore.i42 + moore.assign %2, %v : i42 + %2 = moore.net wire : + %7 = moore.read %2 : + dbg.variable "e", %7 : !moore.i42 +} + +// CHECK-LABEL: moore.module @DontOptimizeVarsWithMultipleAssigns +moore.module @DontOptimizeVarsWithMultipleAssigns() { + %0 = moore.constant 1337 : i42 + %1 = moore.constant 9001 : i42 + + // CHECK: %a = moore.variable + // CHECK: moore.assign %a + // CHECK: moore.assign %a + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: dbg.variable "a", [[TMP]] + %a = moore.variable : + moore.assign %a, %0 : i42 + moore.assign %a, %1 : i42 + %2 = moore.read %a : + dbg.variable "a", %2 : !moore.i42 + + // CHECK: %b = moore.net + // CHECK: moore.assign %b + // CHECK: moore.assign %b + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: dbg.variable "b", [[TMP]] + %b = moore.net wire : + moore.assign %b, %0 : i42 + moore.assign %b, %1 : i42 + %3 = moore.read %b : + dbg.variable "b", %3 : !moore.i42 + + // CHECK: %c = moore.net + // CHECK: moore.assign %c + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: dbg.variable "c", [[TMP]] + %c = moore.net wire %0 : + moore.assign %c, %1 : i42 + %4 = moore.read %c : + dbg.variable "c", %4 : !moore.i42 +} + +// CHECK-LABEL: moore.module @DontOptimizeVarsWithNonReadUses +moore.module @DontOptimizeVarsWithNonReadUses(in %u: !moore.i42, in %v: !moore.i42) { + // CHECK: %a = moore.variable + // CHECK: moore.assign %a, %u + // CHECK: func.call @useRef(%a) + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: dbg.variable "a", [[TMP]] + %a = moore.variable : + moore.assign %a, %u : i42 + func.call @useRef(%a) : (!moore.ref) -> () + %2 = moore.read %a : + dbg.variable "a", %2 : !moore.i42 + + // CHECK: %b = moore.net wire %u + // CHECK: moore.assign %b, %v + // CHECK: func.call @useRef(%b) + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: dbg.variable "b", [[TMP]] + %b = moore.net wire %u : + moore.assign %b, %v : i42 + func.call @useRef(%b) : (!moore.ref) -> () + %3 = moore.read %b : + dbg.variable "b", %3 : !moore.i42 + + // Unique continuous assigns should be folded into net definitions even if the + // net has non-read uses. + // CHECK: %c = moore.net wire %u + // CHECK-NOT: moore.assign %c + // CHECK: func.call @useRef(%c) + %c = moore.net wire : + moore.assign %c, %u : i42 + func.call @useRef(%c) : (!moore.ref) -> () } -// CHECK-LABEL: moore.module @MultiAssign -moore.module @MultiAssign() { +func.func private @useRef(%arg0: !moore.ref) + +// CHECK-LABEL: moore.module @DropRedundantVars +moore.module @DropRedundantVars(in %a : !moore.i42, out b : !moore.i42, out c : !moore.i42) { + // Remove variables that shadow an input port of the same name. // CHECK-NOT: moore.assigned_variable - // CHECK: %a = moore.variable : - %a = moore.variable : - // CHECK: %0 = moore.constant 32 : i32 - %0 = moore.constant 32 : i32 - // CHECK: moore.assign %a, %0 : i32 - moore.assign %a, %0 : i32 - // CHECK: %1 = moore.constant 64 : i32 - %1 = moore.constant 64 : i32 - // CHECK: moore.assign %a, %1 : i32 - moore.assign %a, %1 : i32 - moore.output + // CHECK: dbg.variable "a", %a + %0 = moore.assigned_variable name "a" %a : i42 + dbg.variable "a", %0 : !moore.i42 + + // Variables that shadow an input port of a different name should remain. + // CHECK: %a2 = moore.assigned_variable + // CHECK: dbg.variable "a2", %a + %a2 = moore.assigned_variable %a : i42 + dbg.variable "a2", %a2 : !moore.i42 + + // Chained variables with the same name should be reduced to just one. + // CHECK: %v = moore.assigned_variable %a + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "v", %v + %1 = moore.assigned_variable name "v" %a : i42 + %2 = moore.assigned_variable name "v" %1 : i42 + dbg.variable "v", %2 : !moore.i42 + + // Remove variables that shadow an output port of the same name. Variables + // that shadow an output port of a different name should remain. + // CHECK: [[TMP:%.+]] = moore.constant 9001 : i42 + // CHECK-NOT: %b = moore.assigned_variable + // CHECK: %w = moore.assigned_variable [[TMP]] + // CHECK: moore.output [[TMP]], %w + %3 = moore.constant 9001 : i42 + %b = moore.assigned_variable %3 : i42 + %w = moore.assigned_variable %3 : i42 + moore.output %b, %w : !moore.i42, !moore.i42 } // CHECK-LABEL: func.func @StructExtractFold1