[Moore] Add more AssignedVariableOp canonicalizations (#7477)

Change the `AssignedVariableOp` to directly return type `T` instead of
the `ref<T>`. This removes the implied allocation and assignment, and
makes this op behave essentially like `hw.wire`.

The canonicalizers for `VariableOp` and `NetOp` can now replace all
reads from the old variable or net with the value of
`AssignedVariableOp` directly, since there is no more `ref<T>` type
involved. This also allows us to fully eliminate unnamed variables and
nets that have a unique continuous assignment.

Also add canonicalizers that remove `AssignedVariableOp` if they shadow
an input or output port of the same name, or if there are multiple such
variables with the same name in a chain.

At a later stage we may want to replace `AssignedVariableOp` entirely
with `dbg.variable`.
This commit is contained in:
Fabian Schuiki 2024-08-09 14:18:23 -07:00 committed by GitHub
parent 562036cde8
commit 450c968235
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 309 additions and 45 deletions

View File

@ -266,19 +266,20 @@ def NetOp : MooreOp<"net", [
`` custom<ImplicitSSAName>($name) $kind ($assignment^)? attr-dict
`:` type($result)
}];
let hasCanonicalizeMethod = true;
}
def AssignedVarOp : MooreOp<"assigned_variable", [
def AssignedVariableOp : MooreOp<"assigned_variable", [
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
TypesMatchWith<"initial value and variable types match",
"result", "initial", "cast<RefType>($_self).getNestedType()">
SameOperandsAndResultType
]> {
let summary = "The copy of variable must have the initial value";
let arguments = (ins OptionalAttr<StrAttr>:$name, UnpackedType:$initial);
let results = (outs Res<RefType, "", [MemAlloc]>:$result);
let summary = "A variable with a unique continuously assigned value";
let arguments = (ins OptionalAttr<StrAttr>:$name, UnpackedType:$input);
let results = (outs UnpackedType:$result);
let assemblyFormat = [{
`` custom<ImplicitSSAName>($name) $initial attr-dict `:` type($result)
`` custom<ImplicitSSAName>($name) $input attr-dict `:` type($input)
}];
let hasCanonicalizeMethod = true;
}
def ReadOp : MooreOp<"read", [

View File

@ -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<ContinuousAssignOp>(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<ContinuousAssignOp>(user)) {
if (uniqueAssignOp)
return failure();
initial = user->getOperand(1);
uniqueAssignOp = assignOp;
continue;
}
if (initial) {
rewriter.replaceOpWithNewOp<AssignedVarOp>(op, op.getType(),
op.getNameAttr(), initial);
return success();
// Ensure all other users are reads.
if (!isa<ReadOp>(user))
return failure();
}
if (!uniqueAssignOp)
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<AssignedVariableOp>(
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<ReadOp>(user);
rewriter.replaceOp(readOp, assignedValue);
}
return failure();
// Remove the original variable.
rewriter.eraseOp(op);
return success();
}
SmallVector<DestructurableMemorySlot> 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<ContinuousAssignOp>(user)) {
if (uniqueAssignOp)
return failure();
uniqueAssignOp = assignOp;
continue;
}
// Ensure all other users are reads.
if (!isa<ReadOp>(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<AssignedVariableOp>(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<ReadOp>(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<AssignedVariableOp>()) {
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<BlockArgument>(op.getInput())) {
if (auto moduleOp =
dyn_cast<SVModuleOp>(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<OutputOp>(use.getOwner());
if (!outputOp)
continue;
auto moduleOp = dyn_cast<SVModuleOp>(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
//===----------------------------------------------------------------------===//

View File

@ -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<T>`
// indirection and instead directly propagate the assigned value to readers.
// CHECK-NOT: moore.assign
// CHECK-NOT: moore.variable
// CHECK: %a = moore.assigned_variable %0 : <i32>
%a = moore.variable : <i32>
// 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 : <i42>
%3 = moore.read %a : <i42>
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 : <i42>
%4 = moore.read %b : <i42>
dbg.variable "b", %4 : !moore.i42
// Unique continuous assignments to nets should remove the `ref<T>`
// 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 : <i42>
%5 = moore.read %c : <i42>
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 : <i42>
%6 = moore.read %1 : <i42>
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 : <i42>
%7 = moore.read %2 : <i42>
dbg.variable "e", %7 : !moore.i42
}
// CHECK-LABEL: moore.module @MultiAssign
moore.module @MultiAssign() {
// 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 : <i42>
moore.assign %a, %0 : i42
moore.assign %a, %1 : i42
%2 = moore.read %a : <i42>
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 : <i42>
moore.assign %b, %0 : i42
moore.assign %b, %1 : i42
%3 = moore.read %b : <i42>
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 : <i42>
moore.assign %c, %1 : i42
%4 = moore.read %c : <i42>
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 : <i42>
moore.assign %a, %u : i42
func.call @useRef(%a) : (!moore.ref<i42>) -> ()
%2 = moore.read %a : <i42>
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 : <i42>
moore.assign %b, %v : i42
func.call @useRef(%b) : (!moore.ref<i42>) -> ()
%3 = moore.read %b : <i42>
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 : <i42>
moore.assign %c, %u : i42
func.call @useRef(%c) : (!moore.ref<i42>) -> ()
}
func.func private @useRef(%arg0: !moore.ref<i42>)
// 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 : <i32>
%a = moore.variable : <i32>
// 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