mirror of https://github.com/llvm/circt.git
[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:
parent
562036cde8
commit
450c968235
|
@ -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", [
|
||||
|
|
|
@ -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
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue