Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c386787
Able to get class when you set a class-Name
Jaddyen May 21, 2025
7a97f17
fixes
Jaddyen May 22, 2025
23e8435
Added flags to increase readability
Jaddyen May 27, 2025
0c55dcf
Working tests to emit-class
Jaddyen May 28, 2025
ff237e5
A lil' cleaning up
Jaddyen May 29, 2025
2dc3148
Clarifying TODO messages
Jaddyen May 29, 2025
bf4f1cd
Formatting issues
Jaddyen May 29, 2025
e7f2084
[flang][rt] Fix the use of kNoAsyncId -> kNoAsyncObject (#141079)
clementval May 22, 2025
20d9f7a
Adding ClassOp, FieldOp, GetFieldOp to allow for a transfrom from fun…
Jaddyen Jun 6, 2025
8ba35d7
Removed unnecessary comments
Jaddyen Jun 6, 2025
f3acc4f
rewritten
Jaddyen Jun 12, 2025
f47f211
Added tests for the wrap-emitc-func-in-class
Jaddyen Jun 13, 2025
49f202c
Remove class-name changes
Jaddyen Jun 14, 2025
d108bf2
cleaning up comments
Jaddyen Jun 14, 2025
86a057e
Removed tf attributes and allowed for no attributes
Jaddyen Jun 16, 2025
b064a9c
[flang][rt] Fix the use of kNoAsyncId -> kNoAsyncObject (#141079)
clementval May 22, 2025
4780ab3
Cleaning up descriptions
Jaddyen Jun 17, 2025
009f137
Avoid unnecessary checks
Jaddyen Jun 18, 2025
3bb6799
clean up how we use attributes
Jaddyen Jun 18, 2025
1272578
standards of elide brackets
Jaddyen Jun 18, 2025
bbf6775
Merge remote-tracking branch 'refs/remotes/upstream/main' into adding…
Jaddyen Jun 18, 2025
08f76bb
Update TranslateToCpp.cpp
Jaddyen Jun 18, 2025
e2bcd3a
Update TranslateRegistration.cpp
Jaddyen Jun 18, 2025
d69ca14
Merge branch 'llvm:main' into adding-mlir-models
Jaddyen Jun 20, 2025
c278c1d
Merge branch 'main' into adding-mlir-models
mtrofin Jun 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
Original file line number Diff line number Diff line change
Expand Up @@ -1593,4 +1593,90 @@ def EmitC_SwitchOp : EmitC_Op<"switch", [RecursiveMemoryEffects,
let hasVerifier = 1;
}

def EmitC_ClassOp
: EmitC_Op<"class", [AutomaticAllocationScope, IsolatedFromAbove,
OpAsmOpInterface, SymbolTable,
Symbol]#GraphRegionNoTerminator.traits> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: space around # to make the concat easier to see.

Copy link
Contributor Author

@Jaddyen Jaddyen Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-format gets rid of the space around #.
do we need to ignore clang-format on this?

let summary =
"Represents a C++ class definition, encapsulating fields and methods.";

let description = [{
The `emitc.class` operation defines a C++ class, acting as a container
for its data fields (`emitc.field`) and methods (`emitc.func`).
It creates a distinct scope, isolating its contents from the surrounding
MLIR region, similar to how C++ classes encapsulate their internals.

Example:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: newline in between to ensure the Markdown highlighter using mlir-www triggers.


```mlir
emitc.class @modelClass {
emitc.field @fieldName0 : !emitc.array<1xf32> = {emitc.opaque = "input_tensor"}
emitc.func @execute() {
%0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
%1 = get_field @fieldName0 : !emitc.array<1xf32>
%2 = subscript %1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
return
}
}
```
}];

let arguments = (ins SymbolNameAttr:$sym_name);

let regions = (region AnyRegion:$body);

let extraClassDeclaration = [{
// Returns the body block containing class members and methods.
Block &getBlock();
}];

let hasCustomAssemblyFormat = 1;

let assemblyFormat = [{ $sym_name attr-dict-with-keyword $body }];
}

def EmitC_FieldOp : EmitC_Op<"field", [Symbol]> {
let summary = "A field within a class";
let description = [{
The `emitc.field` operation declares a named field within an `emitc.class`
operation. The field's type must be an EmitC type.

Example:

```mlir
// Example with an attribute:
emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.opaque = "another_feature"}
// Example with no attribute:
emitc.field @fieldName0 : !emitc.array<1xf32>
```
}];

let arguments = (ins SymbolNameAttr:$sym_name, TypeAttr:$type,
OptionalAttr<AnyAttr>:$attrs);

let assemblyFormat = [{ $sym_name `:` $type ($attrs^)? attr-dict}];

let hasVerifier = 1;
}

def EmitC_GetFieldOp
: EmitC_Op<"get_field", [Pure, DeclareOpInterfaceMethods<
SymbolUserOpInterface>]> {
let summary = "Obtain access to a field within a class instance";
let description = [{
The `emitc.get_field` operation retrieves the lvalue of a
named field from a given class instance.

Example:

```mlir
%0 = get_field @fieldName0 : !emitc.array<1xf32>
```
}];

let arguments = (ins FlatSymbolRefAttr:$field_name);
let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>:$result);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have expected the array to be also an lvalue here ... (or an lvalue of an array). Is this complete wrt types needed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in line with the get_globalop. The lvalue type models assignable l values only.

let assemblyFormat = "$field_name `:` type($result) attr-dict";
}

#endif // MLIR_DIALECT_EMITC_IR_EMITC
1 change: 1 addition & 0 deletions mlir/include/mlir/Dialect/EmitC/Transforms/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace mlir {
namespace emitc {

#define GEN_PASS_DECL_FORMEXPRESSIONSPASS
#define GEN_PASS_DECL_WRAPFUNCINCLASSPASS
#include "mlir/Dialect/EmitC/Transforms/Passes.h.inc"

//===----------------------------------------------------------------------===//
Expand Down
38 changes: 38 additions & 0 deletions mlir/include/mlir/Dialect/EmitC/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,42 @@ def FormExpressionsPass : Pass<"form-expressions"> {
let dependentDialects = ["emitc::EmitCDialect"];
}

def WrapFuncInClassPass : Pass<"wrap-emitc-func-in-class"> {
let summary = "Wrap functions in classes, using arguments as fields.";
let description = [{
This pass transforms `emitc.func` operations into `emitc.class` operations.
Function arguments become fields of the class, and the function body is moved
to a new `execute` method within the class.
If the corresponding function argument has attributes (accessed via `argAttrs`),
these attributes are attached to the field operation.
Otherwise, the field is created without additional attributes.

Example:

```mlir
emitc.func @model(%input_data : !emitc.array<1xf32> {emitc.opaque = "input_tensor"}) attributes { } {
%0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
%1 = subscript %input_data[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
return
}
// becomes
emitc.class @modelClass {
emitc.field @input_tensor : !emitc.array<1xf32> {emitc.opaque = "input_tensor"}
emitc.func @execute() {
%0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
%1 = get_field @input_tensor : !emitc.array<1xf32>
%2 = subscript %1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
return
}
}
```
}];
let dependentDialects = ["emitc::EmitCDialect"];
let options = [Option<
"namedAttribute", "named-attribute", "std::string",
/*default=*/"",
"Attribute key used to extract field names from function argument's "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC: why not just use NameLoc's on the function args? Then you don't even need to specify a string.

func @f(%arg: i32 loc("foo"), %bah: f32 loc("baz") ...

gets fields foo and baz, and the arg mapping. You could use this to override that, but the NameLoc's is where I've been often querying the names from.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(actually with that, you may not even a special name, the mechanism is general and not tied to any backend/just convention).

"dictionary attributes">];
}

#endif // MLIR_DIALECT_EMITC_TRANSFORMS_PASSES
4 changes: 4 additions & 0 deletions mlir/include/mlir/Dialect/EmitC/Transforms/Transforms.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ ExpressionOp createExpression(Operation *op, OpBuilder &builder);
/// Populates `patterns` with expression-related patterns.
void populateExpressionPatterns(RewritePatternSet &patterns);

/// Populates 'patterns' with func-related patterns.
void populateFuncPatterns(RewritePatternSet &patterns,
StringRef namedAttribute);

} // namespace emitc
} // namespace mlir

Expand Down
43 changes: 43 additions & 0 deletions mlir/lib/Dialect/EmitC/IR/EmitC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,49 @@ void FileOp::build(OpBuilder &builder, OperationState &state, StringRef id) {
builder.getNamedAttr("id", builder.getStringAttr(id)));
}

//===----------------------------------------------------------------------===//
// FieldOp
//===----------------------------------------------------------------------===//
LogicalResult FieldOp::verify() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you share some of this with the verifier of the GlobalOp?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite. Currently, the FieldOp is different from the GlobalOp with respect to what we need to verify. This is due to the change I've made in the most recent commit. We no longer have an initial value in the FieldOp. Instead, we jus have attributes. Due to this, the FieldOp would be different from the GlobalOp and the verify would be different.

if (!isSupportedEmitCType(getType()))
return emitOpError("expected valid emitc type");

Operation *parentOp = getOperation()->getParentOp();
if (!parentOp || !isa<emitc::ClassOp>(parentOp))
return emitOpError("field must be nested within an emitc.class operation");

StringAttr symName = getSymNameAttr();
if (!symName || symName.getValue().empty())
return emitOpError("field must have a non-empty symbol name");

if (!getAttrs())
return success();

return success();
}

//===----------------------------------------------------------------------===//
// GetFieldOp
//===----------------------------------------------------------------------===//
LogicalResult GetFieldOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
mlir::FlatSymbolRefAttr fieldNameAttr = getFieldNameAttr();
FieldOp fieldOp =
symbolTable.lookupNearestSymbolFrom<FieldOp>(*this, fieldNameAttr);
if (!fieldOp)
return emitOpError("field '")
<< fieldNameAttr << "' not found in the class";

Type getFieldResultType = getResult().getType();
Type fieldType = fieldOp.getType();

if (fieldType != getFieldResultType)
return emitOpError("result type ")
<< getFieldResultType << " does not match field '" << fieldNameAttr
<< "' type " << fieldType;

return success();
}

//===----------------------------------------------------------------------===//
// TableGen'd op method definitions
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions mlir/lib/Dialect/EmitC/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_mlir_dialect_library(MLIREmitCTransforms
Transforms.cpp
FormExpressions.cpp
TypeConversions.cpp
WrapFuncInClass.cpp

ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/EmitC/Transforms
Expand Down
112 changes: 112 additions & 0 deletions mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//===- WrapFuncInClass.cpp - Wrap Emitc Funcs in classes -------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/Dialect/EmitC/Transforms/Passes.h"
#include "mlir/Dialect/EmitC/Transforms/Transforms.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Transforms/WalkPatternRewriteDriver.h"

using namespace mlir;
using namespace emitc;

namespace mlir {
namespace emitc {
#define GEN_PASS_DEF_WRAPFUNCINCLASSPASS
#include "mlir/Dialect/EmitC/Transforms/Passes.h.inc"

namespace {
struct WrapFuncInClassPass
: public impl::WrapFuncInClassPassBase<WrapFuncInClassPass> {
using WrapFuncInClassPassBase::WrapFuncInClassPassBase;
void runOnOperation() override {
Operation *rootOp = getOperation();

RewritePatternSet patterns(&getContext());
populateFuncPatterns(patterns, namedAttribute);

walkAndApplyPatterns(rootOp, std::move(patterns));
}
};

} // namespace
} // namespace emitc
} // namespace mlir

class WrapFuncInClass : public OpRewritePattern<emitc::FuncOp> {
public:
WrapFuncInClass(MLIRContext *context, StringRef attrName)
: OpRewritePattern<emitc::FuncOp>(context), attributeName(attrName) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This results in a copy, you are passing in a const ref and assigning to string. you should be able to use a StringRef here as the pass's lifetime extends beyond patterns.


LogicalResult matchAndRewrite(emitc::FuncOp funcOp,
PatternRewriter &rewriter) const override {

auto className = funcOp.getSymNameAttr().str() + "Class";
ClassOp newClassOp = rewriter.create<ClassOp>(funcOp.getLoc(), className);

SmallVector<std::pair<StringAttr, TypeAttr>> fields;
rewriter.createBlock(&newClassOp.getBody());
rewriter.setInsertionPointToStart(&newClassOp.getBody().front());

auto argAttrs = funcOp.getArgAttrs();
for (auto [idx, val] : llvm::enumerate(funcOp.getArguments())) {
StringAttr fieldName;
Attribute argAttr = nullptr;

fieldName = rewriter.getStringAttr("fieldName" + std::to_string(idx));
if (argAttrs && idx < argAttrs->size())
argAttr = (*argAttrs)[idx];

TypeAttr typeAttr = TypeAttr::get(val.getType());
fields.push_back({fieldName, typeAttr});
rewriter.create<emitc::FieldOp>(funcOp.getLoc(), fieldName, typeAttr,
argAttr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last argument is the initializer, right? Why is the name hint used as the initial value of the field?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed.

}

rewriter.setInsertionPointToEnd(&newClassOp.getBody().front());
FunctionType funcType = funcOp.getFunctionType();
Location loc = funcOp.getLoc();
FuncOp newFuncOp =
rewriter.create<emitc::FuncOp>(loc, ("execute"), funcType);

rewriter.createBlock(&newFuncOp.getBody());
newFuncOp.getBody().takeBody(funcOp.getBody());

rewriter.setInsertionPointToStart(&newFuncOp.getBody().front());
std::vector<Value> newArguments;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reserve required size to avoid resizing vector in loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

newArguments.reserve(fields.size());
for (auto &[fieldName, attr] : fields) {
GetFieldOp arg =
rewriter.create<emitc::GetFieldOp>(loc, attr.getValue(), fieldName);
newArguments.push_back(arg);
}

for (auto [oldArg, newArg] :
llvm::zip(newFuncOp.getArguments(), newArguments)) {
rewriter.replaceAllUsesWith(oldArg, newArg);
}

llvm::BitVector argsToErase(newFuncOp.getNumArguments(), true);
if (failed(newFuncOp.eraseArguments(argsToErase)))
newFuncOp->emitOpError("failed to erase all arguments using BitVector");

rewriter.replaceOp(funcOp, newClassOp);
return success();
}

private:
StringRef attributeName;
};

void mlir::emitc::populateFuncPatterns(RewritePatternSet &patterns,
StringRef namedAttribute) {
patterns.add<WrapFuncInClass>(patterns.getContext(), namedAttribute);
}
40 changes: 40 additions & 0 deletions mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// RUN: mlir-opt --wrap-emitc-func-in-class='named-attribute=emitc.name_hint' %s | FileCheck %s

module attributes { } {
emitc.func @model(%arg0: !emitc.array<1xf32> {emitc.name_hint = "another_feature"},
%arg1: !emitc.array<1xf32> {emitc.name_hint = "some_feature"},
%arg2: !emitc.array<1xf32> {emitc.name_hint = "output_0"}) attributes { } {
%0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
%1 = subscript %arg1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
%2 = load %1 : <f32>
%3 = subscript %arg0[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
%4 = load %3 : <f32>
%5 = add %2, %4 : (f32, f32) -> f32
%6 = subscript %arg2[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
assign %5 : f32 to %6 : <f32>
return
}
}


// CHECK: module {
// CHECK-NEXT: emitc.class @modelClass {
// CHECK-NEXT: emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.name_hint = "another_feature"}
// CHECK-NEXT: emitc.field @fieldName1 : !emitc.array<1xf32> {emitc.name_hint = "some_feature"}
// CHECK-NEXT: emitc.field @fieldName2 : !emitc.array<1xf32> {emitc.name_hint = "output_0"}
// CHECK-NEXT: emitc.func @execute() {
// CHECK-NEXT: get_field @fieldName0 : !emitc.array<1xf32>
// CHECK-NEXT: get_field @fieldName1 : !emitc.array<1xf32>
// CHECK-NEXT: get_field @fieldName2 : !emitc.array<1xf32>
// CHECK-NEXT: "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
// CHECK-NEXT: subscript {{.*}}[{{.*}}] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
// CHECK-NEXT: load {{.*}} : <f32>
// CHECK-NEXT: subscript {{.*}}[{{.*}}] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
// CHECK-NEXT: load {{.*}} : <f32>
// CHECK-NEXT: add {{.*}}, {{.*}} : (f32, f32) -> f32
// CHECK-NEXT: subscript {{.*}}[{{.*}}] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
// CHECK-NEXT: assign {{.*}} : f32 to {{.*}} : <f32>
// CHECK-NEXT: return
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }
17 changes: 17 additions & 0 deletions mlir/test/Dialect/EmitC/wrap_emitc_func_in_class_noAttr.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// RUN: mlir-opt --wrap-emitc-func-in-class %s | FileCheck %s

emitc.func @foo(%arg0 : !emitc.array<1xf32>) {
emitc.call_opaque "bar" (%arg0) : (!emitc.array<1xf32>) -> ()
emitc.return
}

// CHECK: module {
// CHECK-NEXT: emitc.class @fooClass {
// CHECK-NEXT: emitc.field @fieldName0 : !emitc.array<1xf32>
// CHECK-NEXT: emitc.func @execute() {
// CHECK-NEXT: %0 = get_field @fieldName0 : !emitc.array<1xf32>
// CHECK-NEXT: call_opaque "bar"(%0) : (!emitc.array<1xf32>) -> ()
// CHECK-NEXT: return
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }
Loading