Skip to content

Commit cae1185

Browse files
committed
[Moore][ImportVerilog] Implement support for virtual functions
- Introduce `moore.class.methoddecl` **`virtual`** flag. - Add `moore.vtable.load_method` op to fetch a virtual method’s callee. - Importer now emits **indirect calls** for virtual methods; direct calls remain for non-virtual. - Verifier ensures method exists, is `virtual`, and the result **FunctionType** matches the erased ABI. - Tests cover both the `virtual` decl and dynamic dispatch.
1 parent a04eac9 commit cae1185

File tree

5 files changed

+134
-19
lines changed

5 files changed

+134
-19
lines changed

include/circt/Dialect/Moore/MooreOps.td

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2418,13 +2418,13 @@ def ClassPropertyDeclOp
24182418
def ClassMethodDeclOp
24192419
: MooreOp<"class.methoddecl", [Symbol, HasParent<"ClassDeclOp">]> {
24202420
let summary = "Declare a class method";
2421-
24222421
let arguments = (ins SymbolNameAttr:$sym_name,
2423-
TypeAttrOf<FunctionType>:$function_type);
2422+
TypeAttrOf<FunctionType>:$function_type,
2423+
OptionalAttr<UnitAttr>:$is_virtual);
24242424

24252425
let results = (outs);
24262426
let assemblyFormat = [{
2427-
$sym_name `:` $function_type attr-dict
2427+
(`virtual` $is_virtual^)? $sym_name `:` $function_type attr-dict
24282428
}];
24292429
}
24302430

@@ -2495,6 +2495,29 @@ def ClassUpcastOp
24952495
"$instance `:` type($instance) `to` type($result) attr-dict";
24962496
}
24972497

2498+
def VTableLoadMethodOp
2499+
: MooreOp<"vtable.load_method", [Pure,
2500+
DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
2501+
let summary = "Load a virtual method entry from a vtable.";
2502+
let description = [{
2503+
Loads a virtual method function pointer from a vtable.
2504+
The verifier resolves the symbols in the symbol table and ensures that the
2505+
result function type matches the erased ABI.
2506+
}];
2507+
2508+
let arguments = (ins
2509+
ClassHandleType:$object,
2510+
SymbolRefAttr:$methodSym
2511+
);
2512+
2513+
let results = (outs
2514+
Type<CPred<"::llvm::isa<mlir::FunctionType>($_self)">>:$result
2515+
);
2516+
2517+
let assemblyFormat =
2518+
"$object `:` $methodSym `of` type($object) `->` type($result) attr-dict";
2519+
}
2520+
24982521
//===----------------------------------------------------------------------===//
24992522
// String Builtins
25002523
//===----------------------------------------------------------------------===//

lib/Conversion/ImportVerilog/Expressions.cpp

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,7 +1233,7 @@ struct RvalueExprVisitor : public ExprVisitor {
12331233
}
12341234

12351235
/// Build a method call including implicit this argument.
1236-
mlir::func::CallOp
1236+
mlir::CallOpInterface
12371237
buildMethodCall(const slang::ast::SubroutineSymbol *subroutine,
12381238
FunctionLowering *lowering,
12391239
moore::ClassHandleType actualHandleTy, Value actualThisRef,
@@ -1259,16 +1259,22 @@ struct RvalueExprVisitor : public ExprVisitor {
12591259
const bool isVirtual =
12601260
(subroutine->flags & slang::ast::MethodFlags::Virtual) != 0;
12611261

1262-
if (!isVirtual) {
1262+
auto calleeSym = lowering->op.getSymName();
1263+
1264+
if (!isVirtual)
12631265
// Direct (non-virtual) call -> moore.class.call
1264-
auto calleeSym =
1265-
SymbolRefAttr::get(context.getContext(), lowering->op.getSymName());
12661266
return mlir::func::CallOp::create(builder, loc, resultTypes, calleeSym,
12671267
explicitArguments);
1268-
}
12691268

1270-
mlir::emitError(loc) << "virtual method calls not supported";
1271-
return {};
1269+
// Since this is a class method, we know that the function name is the leaf
1270+
// separated by ::
1271+
auto [_, funcName] = calleeSym.rsplit("::");
1272+
1273+
auto method = moore::VTableLoadMethodOp::create(
1274+
builder, loc, funcTy, actualThisRef,
1275+
SymbolRefAttr::get(context.getContext(), funcName));
1276+
return mlir::func::CallIndirectOp::create(builder, loc, method,
1277+
explicitArguments);
12721278
}
12731279

12741280
/// Handle subroutine calls.
@@ -1362,9 +1368,10 @@ struct RvalueExprVisitor : public ExprVisitor {
13621368
lowering->op.getFunctionType().getResults().begin(),
13631369
lowering->op.getFunctionType().getResults().end());
13641370

1365-
mlir::func::CallOp callOp;
1371+
mlir::CallOpInterface callOp;
13661372
if (isMethod) {
1367-
// Class functions -> build func.call with implicit this argument
1373+
// Class functions -> build func.call / func.indirect_call with implicit
1374+
// this argument
13681375
auto [thisRef, tyHandle] = getMethodReceiverTypeHandle(expr);
13691376
callOp = buildMethodCall(subroutine, lowering, tyHandle, thisRef,
13701377
arguments, resultTypes);
@@ -1374,7 +1381,7 @@ struct RvalueExprVisitor : public ExprVisitor {
13741381
mlir::func::CallOp::create(builder, loc, lowering->op, arguments);
13751382
}
13761383

1377-
auto result = resultTypes.size() > 0 ? callOp.getResult(0) : Value{};
1384+
auto result = resultTypes.size() > 0 ? callOp->getOpResult(0) : Value{};
13781385
// For calls to void functions we need to have a value to return from this
13791386
// function. Create a dummy `unrealized_conversion_cast`, which will get
13801387
// deleted again later on.

lib/Conversion/ImportVerilog/Structure.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,11 +1045,6 @@ Context::declareFunction(const slang::ast::SubroutineSymbol &subroutine) {
10451045
return {};
10461046
}
10471047

1048-
if (subroutine.flags.has(slang::ast::MethodFlags::Virtual)) {
1049-
mlir::emitError(loc) << "Virtual methods are not yet supported!";
1050-
return {};
1051-
}
1052-
10531048
// Build qualified name: @"Pkg::Class"::subroutine
10541049
SmallString<64> qualName;
10551050
qualName += ownerDecl.getSymName(); // already qualified
@@ -1566,6 +1561,11 @@ struct ClassDeclVisitor {
15661561
return success();
15671562
}
15681563

1564+
const mlir::UnitAttr isVirtual =
1565+
(fn.flags & slang::ast::MethodFlags::Virtual)
1566+
? UnitAttr::get(context.getContext())
1567+
: nullptr;
1568+
15691569
auto loc = convertLocation(fn.location);
15701570
auto *lowering = context.declareFunction(fn);
15711571
if (!lowering)
@@ -1581,7 +1581,7 @@ struct ClassDeclVisitor {
15811581
FunctionType fnTy = lowering->op.getFunctionType();
15821582

15831583
// Emit the method decl into the class body, preserving source order.
1584-
moore::ClassMethodDeclOp::create(builder, loc, fn.name, fnTy);
1584+
moore::ClassMethodDeclOp::create(builder, loc, fn.name, fnTy, isVirtual);
15851585

15861586
return success();
15871587
}

lib/Dialect/Moore/MooreOps.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,7 @@ ClassUpcastOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
15311531
<< dstTy.getClassSym()
15321532
<< " (destination is not a base class)";
15331533
}
1534+
15341535
LogicalResult
15351536
ClassPropertyRefOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
15361537
// The operand is constrained to ClassHandleType in ODS; unwrap it.
@@ -1584,6 +1585,63 @@ ClassPropertyRefOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
15841585
return success();
15851586
}
15861587

1588+
LogicalResult
1589+
VTableLoadMethodOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
1590+
Operation *op = getOperation();
1591+
1592+
auto object = getObject();
1593+
auto implSym = object.getType().getClassSym();
1594+
1595+
// Check that classdecl of class handle exists
1596+
Operation *implOp = symbolTable.lookupNearestSymbolFrom(op, implSym);
1597+
if (!implOp)
1598+
return emitOpError() << "implementing class " << implSym << " not found";
1599+
auto implClass = cast<moore::ClassDeclOp>(implOp);
1600+
1601+
StringAttr methodName = getMethodSymAttr().getLeafReference();
1602+
if (!methodName || methodName.getValue().empty())
1603+
return emitOpError() << "empty method name";
1604+
1605+
moore::ClassDeclOp cursor = implClass;
1606+
Operation *methodDeclOp = nullptr;
1607+
1608+
// Find method in class decl or parents' class decl
1609+
while (cursor && !methodDeclOp) {
1610+
methodDeclOp = symbolTable.lookupSymbolIn(cursor, methodName);
1611+
if (methodDeclOp)
1612+
break;
1613+
SymbolRefAttr baseSym = cursor.getBaseAttr();
1614+
if (!baseSym)
1615+
break;
1616+
Operation *baseOp = symbolTable.lookupNearestSymbolFrom(op, baseSym);
1617+
cursor = baseOp ? cast<moore::ClassDeclOp>(baseOp) : moore::ClassDeclOp();
1618+
}
1619+
1620+
if (!methodDeclOp)
1621+
return emitOpError() << "no method `" << methodName << "` found in "
1622+
<< implClass.getSymName() << " or its bases";
1623+
1624+
// Make sure method decl is a ClassMethodDeclOp
1625+
auto methodDecl = dyn_cast<moore::ClassMethodDeclOp>(methodDeclOp);
1626+
if (!methodDecl)
1627+
return emitOpError() << "`" << methodName
1628+
<< "` is not a method declaration";
1629+
1630+
// Make sure method decl is marked as virtual
1631+
if (!methodDecl.getIsVirtual())
1632+
return emitOpError() << "method `" << methodName
1633+
<< "` is not declared 'virtual'";
1634+
1635+
// Make sure method signature matches
1636+
auto resFnTy = cast<FunctionType>(getResult().getType());
1637+
auto declFnTy = cast<FunctionType>(methodDecl.getFunctionType());
1638+
if (resFnTy != declFnTy)
1639+
return emitOpError() << "result type " << resFnTy
1640+
<< " does not match method erased ABI " << declFnTy;
1641+
1642+
return success();
1643+
}
1644+
15871645
//===----------------------------------------------------------------------===//
15881646
// TableGen generated logic.
15891647
//===----------------------------------------------------------------------===//

test/Conversion/ImportVerilog/classes.sv

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,3 +557,30 @@ endclass
557557
class testTypedClass extends testClassType;
558558
bool a;
559559
endclass
560+
561+
/// Check virtual attribute of methoddecl
562+
563+
// CHECK-LABEL: moore.class.classdecl @testClassVirtual {
564+
// CHECK-NEXT: moore.class.methoddecl virtual @testFun : (!moore.class<@testClassVirtual>) -> ()
565+
// CHECK: }
566+
// CHECK: func.func private @"testClassVirtual::testFun"(%arg0: !moore.class<@testClassVirtual>) {
567+
// CHECK: return
568+
// CHECK: }
569+
570+
class testClassVirtual;
571+
virtual function void testFun();
572+
endfunction
573+
endclass
574+
575+
/// Check virtual dispatch
576+
577+
// CHECK-LABEL: func.func private @testVirtualDispatch
578+
// CHECK-SAME: (%arg0: !moore.class<@testClassVirtual>) {
579+
// CHECK-NEXT: [[VMETH:%.+]] = moore.vtable.load_method %arg0 : @testFun of <@testClassVirtual> -> (!moore.class<@testClassVirtual>) -> ()
580+
// CHECK-NEXT: call_indirect [[VMETH]](%arg0) : (!moore.class<@testClassVirtual>) -> ()
581+
// CHECK-NEXT: return
582+
// CHECK-NEXT: }
583+
584+
function void testVirtualDispatch (testClassVirtual t);
585+
t.testFun();
586+
endfunction

0 commit comments

Comments
 (0)