Skip to content

Commit 09ee6e7

Browse files
committed
[Moore] Implement ClassHandleType, ClassDeclarationOp
This change introduces first-class **class** support to the Moore dialect and teaches the Verilog importer to **recognize and lower SV classes** into that IR. It also adds an opaque **class handle type** for values of class objects. - New **IR ops** for classes: declarations, properties, and a class-body terminator. - New **type**: `!moore.class.object<@sym>` (with `ref` variant). - Importer now **creates symbols for classes**, wires up `extends`/`implements`, and lowers **class-typed values** to `!moore.class.object<…>`. - Extensive **tests** for inheritance, interfaces, properties, scoping, and handle declarations.
1 parent 479e9f3 commit 09ee6e7

File tree

9 files changed

+538
-2
lines changed

9 files changed

+538
-2
lines changed

include/circt/Dialect/Moore/MooreOps.td

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2354,4 +2354,46 @@ def AtanhBIOp : RealMathFunc<"atanh"> {
23542354
let summary = "Arc-hyperbolic tangent";
23552355
}
23562356

2357+
//===----------------------------------------------------------------------===//
2358+
// Classes
2359+
//===----------------------------------------------------------------------===//
2360+
2361+
def ClassPropertyDeclOp
2362+
: MooreOp<"class.propertydecl", [Symbol, HasParent<"ClassDeclOp">]> {
2363+
let summary = "Declare a class property";
2364+
let description = [{
2365+
Declares a property within a class declaration.
2366+
}];
2367+
2368+
let arguments = (ins SymbolNameAttr:$sym_name, TypeAttr:$type);
2369+
2370+
let results = (outs);
2371+
let assemblyFormat = [{
2372+
$sym_name `:` $type attr-dict
2373+
}];
2374+
2375+
let extraClassDeclaration = [{
2376+
::mlir::Type getPropertyType() { return getTypeAttr().getValue(); }
2377+
}];
2378+
}
2379+
2380+
def ClassDeclOp
2381+
: MooreOp<"class.classdecl", [Symbol, SymbolTable, IsolatedFromAbove,
2382+
NoTerminator, SingleBlock]> {
2383+
let summary = "Class declaration";
2384+
let arguments = (ins SymbolNameAttr:$sym_name,
2385+
OptionalAttr<SymbolRefAttr>:$base,
2386+
OptionalAttr<SymbolRefArrayAttr>:$implementedInterfaces);
2387+
let results = (outs);
2388+
let regions = (region AnyRegion:$body);
2389+
let assemblyFormat = [{
2390+
$sym_name
2391+
(`extends` $base^)?
2392+
(`implements` $implementedInterfaces^)?
2393+
attr-dict-with-keyword $body
2394+
}];
2395+
let hasVerifier = 1;
2396+
}
2397+
2398+
23572399
#endif // CIRCT_DIALECT_MOORE_MOOREOPS

include/circt/Dialect/Moore/MooreTypes.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class UnpackedArrayType;
4343
class UnpackedStructType;
4444
class UnpackedUnionType;
4545
class VoidType;
46+
class ClassHandleType;
4647

4748
/// The number of values each bit of a type can assume.
4849
enum class Domain {
@@ -102,7 +103,8 @@ class UnpackedType : public Type {
102103
static bool classof(Type type) {
103104
return llvm::isa<PackedType, StringType, ChandleType, EventType, RealType,
104105
UnpackedArrayType, OpenUnpackedArrayType, AssocArrayType,
105-
QueueType, UnpackedStructType, UnpackedUnionType>(type);
106+
QueueType, UnpackedStructType, UnpackedUnionType,
107+
ClassHandleType>(type);
106108
}
107109

108110
/// Get the value domain of this type.

include/circt/Dialect/Moore/MooreTypes.td

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ def EventType : MooreTypeDef<"Event", [], "moore::UnpackedType"> {
5252
let summary = "the SystemVerilog `event` type";
5353
}
5454

55+
//===----------------------------------------------------------------------===//
56+
// ClassHandleType
57+
//===----------------------------------------------------------------------===//
58+
59+
def ClassHandleType : MooreTypeDef<"ClassHandle", [], "moore::UnpackedType"> {
60+
let mnemonic = "class";
61+
let summary = "Class object handle type, pointing to an object on the heap.";
62+
let description = [{
63+
The `!moore.class<@C>` type represents a handle of a class instance of class `@C` living on the program's heap.
64+
}];
65+
let parameters = (ins AttrParameter<"::mlir::SymbolRefAttr", "class symbol">:$classSym);
66+
let assemblyFormat = "`<` $classSym `>`";
67+
}
5568

5669
//===----------------------------------------------------------------------===//
5770
// TimeType
@@ -559,4 +572,6 @@ def UnionRefType : SpecificRefType<UnionType>;
559572
def UnpackedUnionRefType : SpecificRefType<UnpackedUnionType>;
560573
def AnyUnionRefType : SpecificRefType<AnyUnionType>;
561574

575+
/// Class references.
576+
def ClassHandleRefType : SpecificRefType<ClassHandleType>;
562577
#endif // CIRCT_DIALECT_MOORE_MOORETYPES

lib/Conversion/ImportVerilog/ImportVerilogInternals.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ struct FunctionLowering {
5555
bool isConverting = false;
5656
};
5757

58+
// Class lowering information.
59+
struct ClassLowering {
60+
circt::moore::ClassDeclOp op;
61+
};
62+
5863
/// Information about a loops continuation and exit blocks relevant while
5964
/// lowering the loop's body statements.
6065
struct LoopFrame {
@@ -115,6 +120,8 @@ struct Context {
115120
declareFunction(const slang::ast::SubroutineSymbol &subroutine);
116121
LogicalResult convertFunction(const slang::ast::SubroutineSymbol &subroutine);
117122
LogicalResult finalizeFunctionBodyCaptures(FunctionLowering &lowering);
123+
LogicalResult convertClassDeclaration(const slang::ast::ClassType &classdecl);
124+
ClassLowering *declareClass(const slang::ast::ClassType &cls);
118125

119126
// Convert a statement AST node to MLIR ops.
120127
LogicalResult convertStatement(const slang::ast::Statement &stmt);
@@ -252,6 +259,10 @@ struct Context {
252259
std::unique_ptr<FunctionLowering>>
253260
functions;
254261

262+
/// Classes that have already been converted.
263+
DenseMap<const slang::ast::ClassType *, std::unique_ptr<ClassLowering>>
264+
classes;
265+
255266
/// A table of defined values, such as variables, that may be referred to by
256267
/// name in expressions. The expressions use this table to lookup the MLIR
257268
/// value that was created for a given declaration in the Slang AST node.
@@ -298,5 +309,4 @@ struct Context {
298309

299310
} // namespace ImportVerilog
300311
} // namespace circt
301-
302312
#endif // CONVERSION_IMPORTVERILOG_IMPORTVERILOGINTERNALS_H

lib/Conversion/ImportVerilog/Structure.cpp

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "ImportVerilogInternals.h"
1010
#include "slang/ast/Compilation.h"
11+
#include "slang/ast/symbols/ClassSymbols.h"
1112
#include "llvm/ADT/ScopeExit.h"
1213

1314
using namespace circt;
@@ -54,6 +55,11 @@ struct BaseVisitor {
5455
return success();
5556
}
5657

58+
// Handle classes without parameters
59+
LogicalResult visit(const slang::ast::ClassType &classdecl) {
60+
return context.convertClassDeclaration(classdecl);
61+
}
62+
5763
// Skip typedefs.
5864
LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
5965
LogicalResult visit(const slang::ast::ForwardingTypedefSymbol &) {
@@ -1309,3 +1315,204 @@ Context::finalizeFunctionBodyCaptures(FunctionLowering &lowering) {
13091315

13101316
return success();
13111317
}
1318+
1319+
namespace {
1320+
1321+
/// Construct a fully qualified class name containing the instance hierarchy
1322+
/// and the class name formatted as H1::H2::@C
1323+
mlir::StringAttr fullyQualifiedClassName(Context &ctx,
1324+
const slang::ast::Type &ty) {
1325+
SmallString<64> name;
1326+
SmallVector<llvm::StringRef, 8> parts;
1327+
1328+
const slang::ast::Scope *scope = ty.getParentScope();
1329+
while (scope) {
1330+
const auto &sym = scope->asSymbol();
1331+
switch (sym.kind) {
1332+
case slang::ast::SymbolKind::Root:
1333+
scope = nullptr; // stop at $root
1334+
continue;
1335+
case slang::ast::SymbolKind::InstanceBody:
1336+
case slang::ast::SymbolKind::Instance:
1337+
case slang::ast::SymbolKind::Package:
1338+
case slang::ast::SymbolKind::ClassType:
1339+
if (!sym.name.empty())
1340+
parts.push_back(sym.name); // keep packages + outer classes
1341+
break;
1342+
default:
1343+
break;
1344+
}
1345+
scope = sym.getParentScope();
1346+
}
1347+
1348+
for (auto p : llvm::reverse(parts)) {
1349+
name += p;
1350+
name += "::";
1351+
}
1352+
name += ty.name; // class’s own name
1353+
return mlir::StringAttr::get(ctx.getContext(), name);
1354+
}
1355+
1356+
/// Helper function to construct the classes fully qualified base class name
1357+
/// and the name of all implemented interface classes
1358+
std::pair<mlir::SymbolRefAttr, mlir::ArrayAttr>
1359+
buildBaseAndImplementsAttrs(Context &context,
1360+
const slang::ast::ClassType &cls) {
1361+
mlir::MLIRContext *ctx = context.getContext();
1362+
1363+
// Base class (if any)
1364+
mlir::SymbolRefAttr base;
1365+
if (const auto *b = cls.getBaseClass())
1366+
base = mlir::SymbolRefAttr::get(fullyQualifiedClassName(context, *b));
1367+
1368+
// Implemented interfaces (if any)
1369+
SmallVector<mlir::Attribute> impls;
1370+
if (auto ifaces = cls.getDeclaredInterfaces(); !ifaces.empty()) {
1371+
impls.reserve(ifaces.size());
1372+
for (const auto *iface : ifaces)
1373+
impls.push_back(mlir::FlatSymbolRefAttr::get(
1374+
fullyQualifiedClassName(context, *iface)));
1375+
}
1376+
1377+
mlir::ArrayAttr implArr =
1378+
impls.empty() ? mlir::ArrayAttr() : mlir::ArrayAttr::get(ctx, impls);
1379+
1380+
return {base, implArr};
1381+
}
1382+
1383+
/// Visit a slang::ast::ClassType and populate the body of an existing
1384+
/// moore::ClassDeclOp with field/method decls.
1385+
struct ClassDeclVisitor {
1386+
Context &context;
1387+
OpBuilder &builder;
1388+
ClassLowering &classLowering;
1389+
1390+
ClassDeclVisitor(Context &ctx, ClassLowering &lowering)
1391+
: context(ctx), builder(ctx.builder), classLowering(lowering) {}
1392+
1393+
LogicalResult run(const slang::ast::ClassType &classAST) {
1394+
OpBuilder::InsertionGuard ig(builder);
1395+
Block *body = classLowering.op.getBody().empty()
1396+
? &classLowering.op.getBody().emplaceBlock()
1397+
: &classLowering.op.getBody().front();
1398+
builder.setInsertionPointToEnd(body);
1399+
1400+
for (const auto &mem : classAST.members())
1401+
if (failed(mem.visit(*this)))
1402+
return failure();
1403+
1404+
return success();
1405+
}
1406+
1407+
// Properties: ClassPropertySymbol
1408+
LogicalResult visit(const slang::ast::ClassPropertySymbol &prop) {
1409+
auto loc = convertLocation(prop.location);
1410+
auto ty = context.convertType(prop.getType());
1411+
if (!ty)
1412+
return failure();
1413+
1414+
moore::ClassPropertyDeclOp::create(builder, loc, prop.name, ty);
1415+
return success();
1416+
}
1417+
1418+
// Fully-fledged functions - SubroutineSymbol
1419+
LogicalResult visit(const slang::ast::SubroutineSymbol &fn) {
1420+
if (fn.flags & slang::ast::MethodFlags::BuiltIn) {
1421+
static bool remarkEmitted = false;
1422+
if (remarkEmitted)
1423+
return success();
1424+
1425+
mlir::emitRemark(classLowering.op.getLoc())
1426+
<< "Class builtin functions (needed for randomization, constraints, "
1427+
"and covergroups) are not yet supported and will be dropped "
1428+
"during lowering.";
1429+
remarkEmitted = true;
1430+
return success();
1431+
}
1432+
return failure();
1433+
}
1434+
1435+
// Nested class definition, convert
1436+
LogicalResult visit(const slang::ast::ClassType &cls) {
1437+
return context.convertClassDeclaration(cls);
1438+
}
1439+
1440+
// Transparent members: ignore (inherited names pulled in by slang)
1441+
LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
1442+
return success();
1443+
}
1444+
1445+
// Empty members: ignore
1446+
LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
1447+
return success();
1448+
}
1449+
1450+
// Emit an error for all other members.
1451+
template <typename T>
1452+
LogicalResult visit(T &&node) {
1453+
Location loc = UnknownLoc::get(context.getContext());
1454+
if constexpr (requires { node.location; })
1455+
loc = convertLocation(node.location);
1456+
mlir::emitError(loc) << "unsupported construct in ClassType members: "
1457+
<< slang::ast::toString(node.kind);
1458+
return failure();
1459+
}
1460+
1461+
private:
1462+
Location convertLocation(const slang::SourceLocation &sloc) {
1463+
return context.convertLocation(sloc);
1464+
}
1465+
};
1466+
} // namespace
1467+
1468+
ClassLowering *Context::declareClass(const slang::ast::ClassType &cls) {
1469+
// Check if there already is a declaration for this class.
1470+
auto &lowering = classes[&cls];
1471+
if (lowering) {
1472+
if (!lowering->op)
1473+
return {};
1474+
return lowering.get();
1475+
}
1476+
1477+
lowering = std::make_unique<ClassLowering>();
1478+
auto loc = convertLocation(cls.location);
1479+
1480+
// Pick an insertion point for this function according to the source file
1481+
// location.
1482+
OpBuilder::InsertionGuard g(builder);
1483+
auto it = orderedRootOps.upper_bound(cls.location);
1484+
if (it == orderedRootOps.end())
1485+
builder.setInsertionPointToEnd(intoModuleOp.getBody());
1486+
else
1487+
builder.setInsertionPoint(it->second);
1488+
1489+
auto symName = fullyQualifiedClassName(*this, cls);
1490+
auto [base, impls] = buildBaseAndImplementsAttrs(*this, cls);
1491+
1492+
auto classDeclOp =
1493+
moore::ClassDeclOp::create(builder, loc, symName, base, impls);
1494+
1495+
SymbolTable::setSymbolVisibility(classDeclOp,
1496+
SymbolTable::Visibility::Public);
1497+
orderedRootOps.insert(it, {cls.location, classDeclOp});
1498+
lowering->op = classDeclOp;
1499+
1500+
symbolTable.insert(classDeclOp);
1501+
return lowering.get();
1502+
}
1503+
1504+
LogicalResult
1505+
Context::convertClassDeclaration(const slang::ast::ClassType &classdecl) {
1506+
1507+
// Keep track of local time scale.
1508+
auto prevTimeScale = timeScale;
1509+
timeScale = classdecl.getTimeScale().value_or(slang::TimeScale());
1510+
auto timeScaleGuard =
1511+
llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
1512+
1513+
auto *lowering = declareClass(classdecl);
1514+
if (failed(ClassDeclVisitor(*this, *lowering).run(classdecl)))
1515+
return failure();
1516+
1517+
return success();
1518+
}

lib/Conversion/ImportVerilog/Types.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@ struct TypeVisitor {
166166
return moore::ChandleType::get(context.getContext());
167167
}
168168

169+
Type visit(const slang::ast::ClassType &type) {
170+
if (auto *lowering = context.declareClass(type)) {
171+
mlir::StringAttr symName = lowering->op.getSymNameAttr();
172+
mlir::FlatSymbolRefAttr symRef = mlir::FlatSymbolRefAttr::get(symName);
173+
return moore::ClassHandleType::get(context.getContext(), symRef);
174+
}
175+
return {};
176+
}
177+
169178
/// Emit an error for all other types.
170179
template <typename T>
171180
Type visit(T &&node) {

lib/Dialect/Moore/MooreOps.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,28 @@ OpFoldResult DivSOp::fold(FoldAdaptor adaptor) {
13661366
return {};
13671367
}
13681368

1369+
//===----------------------------------------------------------------------===//
1370+
// Classes
1371+
//===----------------------------------------------------------------------===//
1372+
1373+
LogicalResult ClassDeclOp::verify() {
1374+
mlir::Region &body = getBody();
1375+
if (body.empty())
1376+
return mlir::success();
1377+
1378+
auto &block = body.front();
1379+
for (mlir::Operation &op : block) {
1380+
1381+
// allow only property and method decls and terminator
1382+
if (llvm::isa<circt::moore::ClassPropertyDeclOp>(&op))
1383+
continue;
1384+
1385+
return emitOpError()
1386+
<< "body may only contain 'moore.class.propertydecl' operations";
1387+
}
1388+
return mlir::success();
1389+
}
1390+
13691391
//===----------------------------------------------------------------------===//
13701392
// TableGen generated logic.
13711393
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)