Skip to content

Commit

Permalink
[FIRRTL] Use set-based logic to test for layer compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
rwy7 committed Feb 7, 2024
1 parent 53d810e commit b1a6d3e
Show file tree
Hide file tree
Showing 4 changed files with 415 additions and 106 deletions.
214 changes: 162 additions & 52 deletions lib/Dialect/FIRRTL/FIRRTLOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/FormatVariadic.h"
Expand Down Expand Up @@ -320,6 +321,128 @@ void getAsmBlockArgumentNamesImpl(Operation *op, mlir::Region &region,
static ParseResult parseNameKind(OpAsmParser &parser,
firrtl::NameKindEnumAttr &result);

//===----------------------------------------------------------------------===//
// Layer Verification Utilities
//===----------------------------------------------------------------------===//

namespace {
struct CompareSymbolRefAttr {
// True if lhs is lexicographically less than rhs.
bool operator()(SymbolRefAttr lhs, SymbolRefAttr rhs) const {
auto cmp = lhs.getRootReference().compare(rhs.getRootReference());
if (cmp == -1)
return true;
if (cmp == 1)
return false;
auto lhsNested = lhs.getNestedReferences();
auto rhsNested = rhs.getNestedReferences();
auto lhsNestedSize = lhsNested.size();
auto rhsNestedSize = rhsNested.size();
auto e = std::min(lhsNestedSize, rhsNestedSize);
for (unsigned i = 0; i < e; ++i) {
auto cmp = lhsNested[i].getAttr().compare(rhsNested[i].getAttr());
if (cmp == -1)
return true;
if (cmp == 1)
return false;
}
return lhsNestedSize < rhsNestedSize;
}
};
} // namespace

using LayerSet = SmallSet<SymbolRefAttr, 4, CompareSymbolRefAttr>;

/// Get the ambient layers active at the given op.
static LayerSet getAmbientLayersAt(Operation *op) {
// Crawl through the parent ops, accumulating all ambient layers at the given
// operation.
LayerSet result;
for (; op != nullptr; op = op->getParentOp()) {
if (auto module = dyn_cast<FModuleLike>(op)) {
auto layers = module.getLayersAttr().getAsRange<SymbolRefAttr>();
result.insert(layers.begin(), layers.end());
break;
}
if (auto layerblock = dyn_cast<LayerBlockOp>(op)) {
result.insert(layerblock.getLayerName());
continue;
}
}
return result;
}

/// Get the ambient layer requirements at the definition site of the value.
static LayerSet getAmbientLayersFor(Value value) {
return getAmbientLayersAt(getFieldRefFromValue(value).getDefiningOp());
}

/// Get the effective layer requirements for the given value.
/// The effective layers for a value is the union of
/// - the ambient layers for the cannonical storage location.
/// - any explicit layer annotations in the value's type.
static LayerSet getLayersFor(Value value) {
auto result = getAmbientLayersFor(value);
if (auto type = dyn_cast<RefType>(value.getType()))
if (auto layer = type.getLayer())
result.insert(type.getLayer());
return result;
}

/// Check that the source layer is compatible with the destination layer.
/// Either the source and destination are identical, or the source-layer
/// is a parent of the destination. For example `A` is compatible with `A.B.C`,
/// because any definition valid in `A` is also valid in `A.B.C`.
static bool isLayerCompatibleWith(mlir::SymbolRefAttr srcLayer,
mlir::SymbolRefAttr dstLayer) {
// A non-colored probe may be cast to any colored probe.
if (!srcLayer)
return true;

// A colored probe cannot be cast to an uncolored probe.
if (!dstLayer)
return false;

// Return true if the srcLayer is a prefix of the dstLayer.
if (srcLayer.getRootReference() != dstLayer.getRootReference())
return false;

auto srcNames = srcLayer.getNestedReferences();
auto dstNames = dstLayer.getNestedReferences();
if (dstNames.size() < srcNames.size())
return false;

return llvm::all_of(llvm::zip_first(srcNames, dstNames),
[](auto x) { return std::get<0>(x) == std::get<1>(x); });
}

/// Check that the source layer is present in the destination layers.
static bool isLayerCompatibleWith(SymbolRefAttr srcLayer,
const LayerSet &dstLayers) {
// fast path: the required layer is directly listed in the provided layers.
if (dstLayers.contains(srcLayer))
return true;

// Slow path: the required layer is not directly listed in the provided
// layers, but the layer may still be provided by a nested layer.
return any_of(dstLayers, [=](SymbolRefAttr dstLayer) {
return isLayerCompatibleWith(srcLayer, dstLayer);
});
}

/// Check that the source layers are all present in the destination layers.
/// True if all source layers are present in the destination.
/// Outputs the set of source layers that are missing in the destination.
static bool isLayerSetCompatibleWith(const LayerSet &src, const LayerSet &dst,
SmallVector<SymbolRefAttr> &missing) {
for (auto srcLayer : src)
if (!isLayerCompatibleWith(srcLayer, dst))
missing.push_back(srcLayer);

llvm::sort(missing, CompareSymbolRefAttr());
return missing.empty();
}

//===----------------------------------------------------------------------===//
// CircuitOp
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -3419,55 +3542,6 @@ LogicalResult StrictConnectOp::verify() {
return success();
}

LogicalResult static verifyLayer(Operation *op, SymbolRefAttr opLayer) {
if (!opLayer)
return success();

auto moduleOp = op->getParentOfType<FModuleOp>();

// All the layers which are currently enabled.
SmallVector<Attribute> enabledLayers(moduleOp.getLayers());

auto layerBlockOp = op->getParentOfType<LayerBlockOp>();
if (layerBlockOp)
enabledLayers.push_back(layerBlockOp.getLayerName());

// The dest layer must be the same as the source layer or a parent of it.
for (Attribute attr : enabledLayers) {
SymbolRefAttr layerPointer = cast<SymbolRefAttr>(attr);
for (;;) {
if (!layerPointer)
break;

if (layerPointer == opLayer)
return success();

if (layerPointer.getNestedReferences().empty()) {
layerPointer = {};
continue;
}
layerPointer =
SymbolRefAttr::get(layerPointer.getRootReference(),
layerPointer.getNestedReferences().drop_back());
}
}

auto diag = op->emitOpError()
<< "cannot interact with layer '" << opLayer
<< "' because it is not inside a '" << moduleOp.getOperationName()
<< "' or '" << LayerBlockOp::getOperationName()
<< "' which enables layer '" << opLayer << "' or a child layer";

if (layerBlockOp)
diag.attachNote(layerBlockOp.getLoc())
<< "the enclosing '" << layerBlockOp.getOperationName()
<< "' is defined here";

return diag.attachNote(moduleOp.getLoc())
<< "the enclosing '" << moduleOp.getOperationName()
<< "' is defined here";
}

LogicalResult RefDefineOp::verify() {
// Check that the flows make sense.
if (failed(checkConnectFlow(*this)))
Expand Down Expand Up @@ -3496,7 +3570,21 @@ LogicalResult RefDefineOp::verify() {
"destination reference cannot be a cast of another reference");
}

return verifyLayer(*this, getDest().getType().getLayer());
// This define is only enabled when its ambient layers are active. Check
// that whenever the destination's layer requirements are met, that this
// op is enabled.
auto ambientLayers = getAmbientLayersAt(getOperation());
auto dstLayers = getLayersFor(getDest());
SmallVector<SymbolRefAttr> missingLayers;
if (!isLayerSetCompatibleWith(ambientLayers, dstLayers, missingLayers)) {
auto diag = emitOpError("has more layer requirements than destination");
auto &note = diag.attachNote();
note << "additional layers required: ";
interleaveComma(missingLayers, note);
return failure();
}

return success();
}

LogicalResult PropAssignOp::verify() {
Expand Down Expand Up @@ -5829,11 +5917,33 @@ FIRRTLType RefSubOp::inferReturnType(ValueRange operands,
}

LogicalResult RefCastOp::verify() {
return verifyLayer(*this, getType().getLayer());
auto srcLayers = getLayersFor(getInput());
auto dstLayers = getLayersFor(getResult());
SmallVector<SymbolRefAttr> missingLayers;
if (!isLayerSetCompatibleWith(srcLayers, dstLayers, missingLayers)) {
auto diag =
emitOpError("cannot discard layer requirements of input reference");
auto &note = diag.attachNote();
note << "discarding layer requirements: ";
llvm::interleaveComma(missingLayers, note);
return failure();
}
return success();
}

LogicalResult RefResolveOp::verify() {
return verifyLayer(*this, getRef().getType().getLayer());
auto srcLayers = getLayersFor(getRef());
auto dstLayers = getAmbientLayersAt(getOperation());
SmallVector<SymbolRefAttr> missingLayers;
if (!isLayerSetCompatibleWith(srcLayers, dstLayers, missingLayers)) {
auto diag =
emitOpError("layer requirements are insufficient to resolve reference");
auto &note = diag.attachNote();
note << "missing layer requirements: ";
interleaveComma(missingLayers, note);
return failure();
}
return success();
}

LogicalResult RWProbeOp::verifyInnerRefs(hw::InnerRefNamespace &ns) {
Expand Down
59 changes: 6 additions & 53 deletions test/Dialect/FIRRTL/errors.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -1906,74 +1906,27 @@ firrtl.circuit "LayerBlockDrivesSinksOutside" {

// -----

firrtl.circuit "IllegalRefDefine" {
firrtl.layer @A bind {}
firrtl.module @Bar(out %a: !firrtl.probe<uint<1>, @A>) {}
// expected-note @below {{the enclosing 'firrtl.module' is defined here}}
firrtl.module @IllegalRefDefine(out %a: !firrtl.probe<uint<1>, @A>) {
%bar_a = firrtl.instance bar interesting_name @Bar(out a: !firrtl.probe<uint<1>, @A>)
// expected-error @below {{'firrtl.ref.define' op cannot interact with layer '@A' because it is not inside a 'firrtl.module' or 'firrtl.layerblock' which enables layer '@A' or a child layer}}
firrtl.ref.define %a, %bar_a : !firrtl.probe<uint<1>, @A>
}
}

// -----

firrtl.circuit "IllegalRefCastDestModule" {
// expected-note @below {{the enclosing 'firrtl.module' is defined here}}
firrtl.module @IllegalRefCastDestModule() {
%a = firrtl.wire : !firrtl.uint<1>
%0 = firrtl.ref.send %a : !firrtl.uint<1>
// expected-error @below {{'firrtl.ref.cast' op cannot interact with layer '@A' because it is not inside a 'firrtl.module' or 'firrtl.layerblock' which enables layer '@A' or a child layer}}
%1 = firrtl.ref.cast %0 : (!firrtl.probe<uint<1>>) -> !firrtl.probe<uint<1>, @A>
}
}

// -----

firrtl.circuit "IllegalRefCastDestLayerBlock" {
firrtl.layer @A bind {
}
firrtl.layer @B bind {
}
// expected-note @below {{the enclosing 'firrtl.module' is defined here}}
firrtl.module @IllegalRefCastDestLayerBlock() {
// expected-note @below {{the enclosing 'firrtl.layerblock' is defined here}}
firrtl.layerblock @A {
%a = firrtl.wire : !firrtl.uint<1>
%0 = firrtl.ref.send %a : !firrtl.uint<1>
// expected-error @below {{'firrtl.ref.cast' op cannot interact with layer '@B' because it is not inside a 'firrtl.module' or 'firrtl.layerblock' which enables layer '@B' or a child layer}}
%1 = firrtl.ref.cast %0 : (!firrtl.probe<uint<1>>) -> !firrtl.probe<uint<1>, @B>
}
}
}

// -----

firrtl.circuit "IllegalRefResolve_NotInLayerBlock" {
firrtl.layer @A bind {
}
// expected-note @below {{the enclosing 'firrtl.module' is defined here}}
firrtl.module @IllegalRefResolve_NotInLayerBlock() {
%0 = firrtl.wire : !firrtl.probe<uint<1>, @A>
// expected-error @below {{'firrtl.ref.resolve' op cannot interact with layer '@A' because it is not inside a 'firrtl.module' or 'firrtl.layerblock' which enables layer '@A' or a child layer}}
// expected-error @below {{'firrtl.ref.resolve' op layer requirements are insufficient to resolve reference}}
// expected-note @below {{missing layer requirements: @A}}
%1 = firrtl.ref.resolve %0 : !firrtl.probe<uint<1>, @A>
}
}

// -----

firrtl.circuit "IllegalRefResolve_IllegalLayer" {
firrtl.layer @A bind {
}
firrtl.layer @B bind {
}
// expected-note @below {{the enclosing 'firrtl.module' is defined here}}
firrtl.layer @A bind {}
firrtl.layer @B bind {}
firrtl.module @IllegalRefResolve_IllegalLayer() {
%0 = firrtl.wire : !firrtl.probe<uint<1>, @A>
// expected-note @below {{the enclosing 'firrtl.layerblock' is defined here}}
firrtl.layerblock @B {
// expected-error @below {{'firrtl.ref.resolve' op cannot interact with layer '@A' because it is not inside a 'firrtl.module' or 'firrtl.layerblock' which enables layer '@A' or a child layer}}
// expected-error @below {{'firrtl.ref.resolve' op layer requirements are insufficient to resolve reference}}
// expected-note @below {{missing layer requirements: @A}}
%1 = firrtl.ref.resolve %0 : !firrtl.probe<uint<1>, @A>
}
}
Expand Down
Loading

0 comments on commit b1a6d3e

Please sign in to comment.