Skip to content

Commit c0f5c6c

Browse files
committed
[Moore] Implement direct class method calls
Adds **class method declarations** and a **direct call op**, wires them into the importer, and supports implicit `this`, inherited property access, and upcasting. - **New ops** - `moore.class.methoddecl`: declare methods inside `classdecl` (FunctionType). - `moore.class.call`: direct (non-virtual) method call; verifies callee, arity, and types. - **Importer** - Lowers `t.m(...)` and unqualified `m(...)` (inside methods) to `class.call`. - Adds implicit `%this : !moore.class<@C>` as first block arg for methods. - Performs **implicit upcast** of the receiver when needed. - Resolves class properties via `class.property_ref` (now also for implicit `this`). - Clearer diag: prints SV type for unsupported member-access bases. - **Lowering plumbing** - `declareFunction` split into `declareCallableImpl` to share logic; supports methods (qualified name `@"Pkg::Class"::method`). - Tracks `currentThisRef` in `Context` for implicit `this` in expressions. - `ClassDeclVisitor` emits `class.methoddecl` in source order after lowering the method body. - **Verifiers / build** - `ClassDeclOp` verifier accepts `class.methoddecl`. - `ClassCallOp` verifier checks symbol -> function, FunctionType, operand types. - Includes/linkage for call interfaces (`MooreOps.h`, CMake). - **Tests** - `classes.sv`: calls on concrete classes, inherited property access through upcast, and method decls reflected in IR.
1 parent feb38f6 commit c0f5c6c

File tree

8 files changed

+420
-30
lines changed

8 files changed

+420
-30
lines changed

include/circt/Dialect/Moore/MooreOps.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
#include "circt/Dialect/Moore/MooreDialect.h"
1919
#include "circt/Dialect/Moore/MooreTypes.h"
2020
#include "mlir/IR/RegionKindInterface.h"
21+
#include "mlir/Interfaces/CallInterfaces.h"
2122
#include "mlir/Interfaces/ControlFlowInterfaces.h"
23+
#include "mlir/Interfaces/FunctionInterfaces.h"
2224
#include "mlir/Interfaces/InferTypeOpInterface.h"
2325
#include "mlir/Interfaces/MemorySlotInterfaces.h"
2426

include/circt/Dialect/Moore/MooreOps.td

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ include "mlir/Interfaces/ControlFlowInterfaces.td"
2020
include "mlir/Interfaces/InferTypeOpInterface.td"
2121
include "mlir/Interfaces/SideEffectInterfaces.td"
2222
include "mlir/Interfaces/MemorySlotInterfaces.td"
23+
include "mlir/Interfaces/CallInterfaces.td"
2324

2425
// Base class for the operations in this dialect.
2526
class MooreOp<string mnemonic, list<Trait> traits = []> :
@@ -2377,6 +2378,20 @@ def ClassPropertyDeclOp
23772378
}];
23782379
}
23792380

2381+
2382+
def ClassMethodDeclOp
2383+
: MooreOp<"class.methoddecl", [Symbol, HasParent<"ClassDeclOp">]> {
2384+
let summary = "Declare a class method";
2385+
2386+
let arguments = (ins SymbolNameAttr:$sym_name,
2387+
TypeAttrOf<FunctionType>:$function_type);
2388+
2389+
let results = (outs);
2390+
let assemblyFormat = [{
2391+
$sym_name `:` $function_type attr-dict
2392+
}];
2393+
}
2394+
23802395
def ClassDeclOp
23812396
: MooreOp<"class.classdecl", [Symbol, SymbolTable, IsolatedFromAbove,
23822397
NoTerminator, SingleBlock]> {

lib/Conversion/ImportVerilog/Expressions.cpp

Lines changed: 131 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,37 @@ struct RvalueExprVisitor : public ExprVisitor {
438438
return value;
439439
}
440440

441+
// We're reading a class property.
442+
if (auto *const property =
443+
expr.symbol.as_if<slang::ast::ClassPropertySymbol>()) {
444+
auto type = context.convertType(*expr.type);
445+
446+
// Get the scope's implicit this variable
447+
mlir::Value instRef = context.getImplicitThisRef();
448+
if (!instRef) {
449+
mlir::emitError(loc) << "class property '" << property->name
450+
<< "' referenced without an implicit 'this'";
451+
return {};
452+
}
453+
454+
auto fieldSym =
455+
mlir::FlatSymbolRefAttr::get(builder.getContext(), property->name);
456+
auto fieldTy = cast<moore::UnpackedType>(type);
457+
auto fieldRefTy = moore::RefType::get(fieldTy);
458+
459+
moore::ClassHandleType classTy =
460+
cast<moore::ClassHandleType>(instRef.getType());
461+
462+
auto targetClassHandle =
463+
context.getAncestorClassWithProperty(classTy, property->name);
464+
auto upcastRef = context.materializeConversion(targetClassHandle, instRef,
465+
false, instRef.getLoc());
466+
467+
Value fieldRef = moore::ClassPropertyRefOp::create(
468+
builder, loc, fieldRefTy, upcastRef, fieldSym);
469+
return moore::ReadOp::create(builder, loc, fieldRef).getResult();
470+
}
471+
441472
// Try to materialize constant values directly.
442473
auto constant = context.evaluateConstant(expr);
443474
if (auto value = context.materializeConstant(constant, *expr.type, loc))
@@ -1155,12 +1186,6 @@ struct RvalueExprVisitor : public ExprVisitor {
11551186

11561187
/// Handle calls.
11571188
Value visit(const slang::ast::CallExpression &expr) {
1158-
// Class method calls are currently not supported.
1159-
if (expr.thisClass()) {
1160-
mlir::emitError(loc, "unsupported class method call");
1161-
return {};
1162-
}
1163-
11641189
// Try to materialize constant values directly.
11651190
auto constant = context.evaluateConstant(expr);
11661191
if (auto value = context.materializeConstant(constant, *expr.type, loc))
@@ -1171,9 +1196,90 @@ struct RvalueExprVisitor : public ExprVisitor {
11711196
expr.subroutine);
11721197
}
11731198

1199+
/// Get both the actual `this` argument of a method call and the required
1200+
/// class type.
1201+
std::pair<mlir::Value, moore::ClassHandleType>
1202+
getMethodReceiverTypeHandle(const slang::ast::CallExpression &expr) {
1203+
1204+
moore::ClassHandleType handleTy;
1205+
mlir::Value thisRef;
1206+
1207+
// Qualified call: t.m(...), extract from thisClass.
1208+
if (const slang::ast::Expression *recvExpr = expr.thisClass()) {
1209+
thisRef = context.convertRvalueExpression(*recvExpr);
1210+
if (!thisRef)
1211+
return {};
1212+
1213+
handleTy = dyn_cast<moore::ClassHandleType>(thisRef.getType());
1214+
if (!handleTy) {
1215+
mlir::emitError(loc)
1216+
<< "receiver of method '" << expr.getSubroutineName()
1217+
<< "' must be class<...>, got " << thisRef.getType();
1218+
return {};
1219+
}
1220+
} else {
1221+
// Unqualified call inside a method body: try using implicit %this.
1222+
thisRef = context.getImplicitThisRef();
1223+
if (!thisRef) {
1224+
mlir::emitError(loc) << "method '" << expr.getSubroutineName()
1225+
<< "' called without an object";
1226+
return {};
1227+
}
1228+
handleTy = dyn_cast<moore::ClassHandleType>(thisRef.getType());
1229+
if (!handleTy) {
1230+
mlir::emitError(loc)
1231+
<< "implicit 'this' must be class<...>, got " << thisRef.getType();
1232+
return {};
1233+
}
1234+
}
1235+
return {thisRef, handleTy};
1236+
}
1237+
1238+
/// Build a method call including implicit this argument.
1239+
mlir::func::CallOp
1240+
buildMethodCall(const slang::ast::SubroutineSymbol *subroutine,
1241+
FunctionLowering *lowering,
1242+
moore::ClassHandleType actualHandleTy, Value actualThisRef,
1243+
SmallVector<Value> &arguments,
1244+
SmallVector<Type> &resultTypes) {
1245+
1246+
// Get the expected receiver type from the lowered method
1247+
auto funcTy = lowering->op.getFunctionType();
1248+
auto expected0 = funcTy.getInput(0);
1249+
auto expectedHdlTy = cast<moore::ClassHandleType>(expected0);
1250+
1251+
// Upcast the handle as necessary.
1252+
auto implicitThisRef = context.materializeConversion(
1253+
expectedHdlTy, actualThisRef, false, actualThisRef.getLoc());
1254+
1255+
// Build an argument list where the this reference is the first argument.
1256+
SmallVector<Value> explicitArguments;
1257+
explicitArguments.reserve(arguments.size() + 1);
1258+
explicitArguments.push_back(implicitThisRef);
1259+
explicitArguments.append(arguments.begin(), arguments.end());
1260+
1261+
// Method call: choose direct vs virtual.
1262+
const bool isVirtual =
1263+
(subroutine->flags & slang::ast::MethodFlags::Virtual) != 0;
1264+
1265+
if (!isVirtual) {
1266+
// Direct (non-virtual) call -> moore.class.call
1267+
auto calleeSym =
1268+
SymbolRefAttr::get(context.getContext(), lowering->op.getSymName());
1269+
return mlir::func::CallOp::create(builder, loc, resultTypes, calleeSym,
1270+
explicitArguments);
1271+
}
1272+
1273+
mlir::emitError(loc) << "virtual method calls not supported";
1274+
return {};
1275+
}
1276+
11741277
/// Handle subroutine calls.
11751278
Value visitCall(const slang::ast::CallExpression &expr,
11761279
const slang::ast::SubroutineSymbol *subroutine) {
1280+
1281+
const bool isMethod = (subroutine->thisVar != nullptr);
1282+
11771283
auto *lowering = context.declareFunction(*subroutine);
11781284
if (!lowering)
11791285
return {};
@@ -1254,20 +1360,34 @@ struct RvalueExprVisitor : public ExprVisitor {
12541360
}
12551361
}
12561362

1257-
// Create the call.
1258-
auto callOp =
1259-
mlir::func::CallOp::create(builder, loc, lowering->op, arguments);
1363+
// Determine result types from the declared/converted func op.
1364+
SmallVector<Type> resultTypes(
1365+
lowering->op.getFunctionType().getResults().begin(),
1366+
lowering->op.getFunctionType().getResults().end());
1367+
1368+
mlir::func::CallOp callOp;
1369+
if (isMethod) {
1370+
// Class functions -> build func.call with implicit this argument
1371+
auto [thisRef, tyHandle] = getMethodReceiverTypeHandle(expr);
1372+
callOp = buildMethodCall(subroutine, lowering, tyHandle, thisRef,
1373+
arguments, resultTypes);
1374+
} else {
1375+
// Free function -> func.call
1376+
callOp =
1377+
mlir::func::CallOp::create(builder, loc, lowering->op, arguments);
1378+
}
12601379

1380+
auto result = resultTypes.size() > 0 ? callOp.getResult(0) : Value{};
12611381
// For calls to void functions we need to have a value to return from this
12621382
// function. Create a dummy `unrealized_conversion_cast`, which will get
12631383
// deleted again later on.
1264-
if (callOp.getNumResults() == 0)
1384+
if (resultTypes.size() == 0)
12651385
return mlir::UnrealizedConversionCastOp::create(
12661386
builder, loc, moore::VoidType::get(context.getContext()),
12671387
ValueRange{})
12681388
.getResult(0);
12691389

1270-
return callOp.getResult(0);
1390+
return result;
12711391
}
12721392

12731393
/// Handle system calls.

lib/Conversion/ImportVerilog/ImportVerilogInternals.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ struct Context {
134134
getAncestorClassWithProperty(const moore::ClassHandleType &actualTy,
135135
StringRef fieldName);
136136

137+
Value getImplicitThisRef() const {
138+
return currentThisRef; // block arg added in declareFunction
139+
}
137140
// Convert a statement AST node to MLIR ops.
138141
LogicalResult convertStatement(const slang::ast::Statement &stmt);
139142

@@ -316,6 +319,17 @@ struct Context {
316319

317320
/// The time scale currently in effect.
318321
slang::TimeScale timeScale;
322+
323+
private:
324+
/// Helper function to extract the commonalities in lowering of functions and
325+
/// methods
326+
FunctionLowering *
327+
declareCallableImpl(const slang::ast::SubroutineSymbol &subroutine,
328+
mlir::StringRef qualifiedName,
329+
llvm::SmallVectorImpl<Type> &extraParams);
330+
/// Variable to track the value of the current function's implicit `this`
331+
/// reference
332+
Value currentThisRef = {};
319333
};
320334

321335
} // namespace ImportVerilog

0 commit comments

Comments
 (0)