[Mlir-commits] [mlir] [mlir][emitc][NFC] Clean up EmitC (PR #152327)

Andrey Timonin llvmlistbot at llvm.org
Wed Aug 6 08:36:27 PDT 2025


https://github.com/EtoAndruwa created https://github.com/llvm/llvm-project/pull/152327

This MR cleans up EmitC source and test files.

**Changes**:
- `mlir/test/mlir-translate/emitc_classops.mlir` was moved to `mlir/test/Target/Cpp/class.mlir`.
- `mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir` and `mlir/test/Dialect/EmitC/wrap_emitc_func_in_class_noAttr.mlir were moved` to `mlir/test/Dialect/EmitC/transforms.mlir`.
- Test for `emitc.class`, `emitc.field` and `emitc.get_field` was added to `mlir/test/Dialect/EmitC/ops.mlir`.
- Unnecessary `-verify-diagnostics` flags were removed from tests.
- Alphabetical order was restored in `mlir/include/mlir/Dialect/EmitC/IR/EmitC.td` and `mlir/lib/Dialect/EmitC/IR/EmitC.cpp`.
- Case 16 in `bool mlir::emitc::isSupportedFloatType(Type type)` in `EmitC.cpp` was refactored to:
```
case 16:
      if (!llvm::isa<Float16Type, BFloat16Type>(type))
        return false;
      LLVM_FALLTHROUGH;
```

>From 5e86788a48d73521af324384ea32bfa0cdb16074 Mon Sep 17 00:00:00 2001
From: EtoAndruwa <timonina1909 at gmail.com>
Date: Wed, 6 Aug 2025 18:13:50 +0300
Subject: [PATCH] [mlir][emitc][NFC] Clean up EmitC

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   | 1486 ++++++++---------
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           | 1000 +++++------
 mlir/test/Dialect/EmitC/attrs.mlir            |    4 +-
 mlir/test/Dialect/EmitC/ops.mlir              |   12 +
 mlir/test/Dialect/EmitC/transforms.mlir       |  247 +--
 mlir/test/Dialect/EmitC/types.mlir            |    6 +-
 .../EmitC/wrap_emitc_func_in_class.mlir       |   40 -
 .../wrap_emitc_func_in_class_noAttr.mlir      |   17 -
 mlir/test/Target/Cpp/class.mlir               |   78 +
 mlir/test/mlir-translate/emitc_classops.mlir  |   78 -
 10 files changed, 1492 insertions(+), 1476 deletions(-)
 delete mode 100644 mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir
 delete mode 100644 mlir/test/Dialect/EmitC/wrap_emitc_func_in_class_noAttr.mlir
 create mode 100644 mlir/test/Target/Cpp/class.mlir
 delete mode 100644 mlir/test/mlir-translate/emitc_classops.mlir

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 937b34a625628..8d5ae60c80452 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -67,52 +67,6 @@ def IntegerIndexOrOpaqueType : Type<CPred<"emitc::isIntegerIndexOrOpaqueType($_s
 "integer, index or opaque type supported by EmitC">;
 def FloatIntegerIndexOrOpaqueType : AnyTypeOf<[EmitCFloatType, IntegerIndexOrOpaqueType]>;
 
-def EmitC_FileOp
-    : EmitC_Op<"file", [IsolatedFromAbove, NoRegionArguments, SymbolTable,
-                        OpAsmOpInterface]#GraphRegionNoTerminator.traits> {
-  let summary = "A file container operation";
-  let description = [{
-    A `file` represents a single C/C++ file.
-
-    `mlir-translate` ignores the body of all `emitc.file` ops
-    unless the `-file-id=id` flag is used. With that flag, all `emitc.file` ops
-    with matching id are emitted.
-
-    Example:
-
-    ```mlir
-    emitc.file "main" {
-      emitc.func @func_one() {
-        emitc.return
-      }
-    }
-    ```
-  }];
-
-  let arguments = (ins Builtin_StringAttr:$id);
-  let regions = (region SizedRegion<1>:$bodyRegion);
-
-  let assemblyFormat = "$id attr-dict-with-keyword $bodyRegion";
-  let builders = [OpBuilder<(ins CArg<"StringRef">:$id)>];
-  let extraClassDeclaration = [{
-    /// Construct a file op from the given location with a name.
-    static FileOp create(Location loc, StringRef name);
-
-    //===------------------------------------------------------------------===//
-    // OpAsmOpInterface Methods
-    //===------------------------------------------------------------------===//
-
-    /// EmitC ops in the body can omit their 'emitc.' prefix in the assembly.
-    static ::llvm::StringRef getDefaultDialect() {
-      return "emitc";
-    }
-  }];
-
-  // We need to ensure that the body region has a block;
-  // the auto-generated builders do not guarantee that.
-  let skipDefaultBuilders = 1;
-}
-
 def EmitC_AddOp : EmitC_BinaryOp<"add", []> {
   let summary = "Addition operation";
   let description = [{
@@ -172,6 +126,35 @@ def EmitC_ApplyOp : EmitC_Op<"apply", [CExpressionInterface]> {
   let hasVerifier = 1;
 }
 
+def EmitC_AssignOp : EmitC_Op<"assign", []> {
+  let summary = "Assign operation";
+  let description = [{
+    The `emitc.assign` operation stores an SSA value to the location designated by an
+    EmitC variable. This operation doesn't return any value. The assigned value
+    must be of the same type as the variable being assigned. The operation is
+    emitted as a C/C++ '=' operator.
+
+    Example:
+
+    ```mlir
+    // Integer variable
+    %0 = "emitc.variable"(){value = 42 : i32} : () -> !emitc.lvalue<i32>
+    %1 = emitc.call_opaque "foo"() : () -> (i32)
+
+    // Assign emitted as `... = ...;`
+    "emitc.assign"(%0, %1) : (!emitc.lvalue<i32>, i32) -> ()
+    ```
+  }];
+
+  let arguments = (ins
+      Res<EmitC_LValueType, "", [MemWrite<DefaultResource, 1, FullEffect>]>:$var,
+      EmitCType:$value);
+  let results = (outs);
+
+  let hasVerifier = 1;
+  let assemblyFormat = "$value `:` type($value) `to` $var `:` type($var) attr-dict";
+}
+
 def EmitC_BitwiseAndOp : EmitC_BinaryOp<"bitwise_and", []> {
   let summary = "Bitwise and operation";
   let description = [{
@@ -280,6 +263,88 @@ def EmitC_BitwiseXorOp : EmitC_BinaryOp<"bitwise_xor", []> {
   }];
 }
 
+def EmitC_CallOp : EmitC_Op<"call",
+    [CallOpInterface, CExpressionInterface,
+     DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
+  let summary = "Call operation";
+  let description = [{
+    The `emitc.call` operation represents a direct call to an `emitc.func`
+    that is within the same symbol scope as the call. The operands and result type
+    of the call must match the specified function type. The callee is encoded as a
+    symbol reference attribute named "callee".
+
+    Example:
+
+    ```mlir
+    %2 = emitc.call @my_add(%0, %1) : (f32, f32) -> f32
+    ```
+  }];
+  let arguments = (ins
+    FlatSymbolRefAttr:$callee,
+    Variadic<EmitCType>:$operands,
+    OptionalAttr<DictArrayAttr>:$arg_attrs,
+    OptionalAttr<DictArrayAttr>:$res_attrs
+  );
+
+  let results = (outs Variadic<EmitCType>);
+
+  let builders = [
+    OpBuilder<(ins "FuncOp":$callee, CArg<"ValueRange", "{}">:$operands), [{
+      $_state.addOperands(operands);
+      $_state.addAttribute("callee", SymbolRefAttr::get(callee));
+      $_state.addTypes(callee.getFunctionType().getResults());
+    }]>,
+    OpBuilder<(ins "SymbolRefAttr":$callee, "TypeRange":$results,
+      CArg<"ValueRange", "{}">:$operands), [{
+      $_state.addOperands(operands);
+      $_state.addAttribute("callee", callee);
+      $_state.addTypes(results);
+    }]>,
+    OpBuilder<(ins "StringAttr":$callee, "TypeRange":$results,
+      CArg<"ValueRange", "{}">:$operands), [{
+      build($_builder, $_state, SymbolRefAttr::get(callee), results, operands);
+    }]>,
+    OpBuilder<(ins "StringRef":$callee, "TypeRange":$results,
+      CArg<"ValueRange", "{}">:$operands), [{
+      build($_builder, $_state, StringAttr::get($_builder.getContext(), callee),
+            results, operands);
+    }]>];
+
+  let extraClassDeclaration = [{
+    FunctionType getCalleeType();
+
+    /// Get the argument operands to the called function.
+    operand_range getArgOperands() {
+      return {arg_operand_begin(), arg_operand_end()};
+    }
+
+    MutableOperandRange getArgOperandsMutable() {
+      return getOperandsMutable();
+    }
+
+    operand_iterator arg_operand_begin() { return operand_begin(); }
+    operand_iterator arg_operand_end() { return operand_end(); }
+
+    /// Return the callee of this operation.
+    CallInterfaceCallable getCallableForCallee() {
+      return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+    }
+
+    /// Set the callee for this operation.
+    void setCalleeFromCallable(CallInterfaceCallable callee) {
+      (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+    }
+
+    bool hasSideEffects() {
+      return false;
+    }
+  }];
+
+  let assemblyFormat = [{
+    $callee `(` $operands `)` attr-dict `:` functional-type($operands, results)
+  }];
+}
+
 def EmitC_CallOpaqueOp : EmitC_Op<"call_opaque", [CExpressionInterface]> {
   let summary = "Opaque call operation";
   let description = [{
@@ -399,6 +464,42 @@ def EmitC_CmpOp : EmitC_BinaryOp<"cmp", []> {
   let assemblyFormat = "$predicate `,` operands attr-dict `:` functional-type(operands, results)";
 }
 
+def EmitC_ConditionalOp : EmitC_Op<"conditional",
+    [AllTypesMatch<["true_value", "false_value", "result"]>, CExpressionInterface]> {
+  let summary = "Conditional (ternary) operation";
+  let description = [{
+    With the `emitc.conditional` operation the ternary conditional operator can
+    be applied.
+
+    Example:
+
+    ```mlir
+    %0 = emitc.cmp gt, %arg0, %arg1 : (i32, i32) -> i1
+
+    %c0 = "emitc.constant"() {value = 10 : i32} : () -> i32
+    %c1 = "emitc.constant"() {value = 11 : i32} : () -> i32
+
+    %1 = emitc.conditional %0, %c0, %c1 : i32
+    ```
+    ```c++
+    // Code emitted for the operations above.
+    bool v3 = v1 > v2;
+    int32_t v4 = 10;
+    int32_t v5 = 11;
+    int32_t v6 = v3 ? v4 : v5;
+    ```
+  }];
+  let arguments = (ins I1:$condition, EmitCType:$true_value, EmitCType:$false_value);
+  let results = (outs EmitCType:$result);
+  let assemblyFormat = "operands attr-dict `:` type($result)";
+
+  let extraClassDeclaration = [{
+    bool hasSideEffects() {
+      return false;
+    }
+  }];
+}
+
 def EmitC_ConstantOp : EmitC_Op<"constant", [ConstantLike]> {
   let summary = "Constant operation";
   let description = [{
@@ -428,43 +529,138 @@ def EmitC_ConstantOp : EmitC_Op<"constant", [ConstantLike]> {
   let hasVerifier = 1;
 }
 
-def EmitC_DivOp : EmitC_BinaryOp<"div", []> {
-  let summary = "Division operation";
+def EmitC_ClassOp
+    : EmitC_Op<"class", [AutomaticAllocationScope, IsolatedFromAbove,
+                         OpAsmOpInterface, SymbolTable,
+                         Symbol]#GraphRegionNoTerminator.traits> {
+  let summary =
+      "Represents a C++ class definition, encapsulating fields and methods.";
+
   let description = [{
-    With the `emitc.div` operation the arithmetic operator / (division) can
-    be applied.
+    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:
 
     ```mlir
-    // Custom form of the division operation.
-    %0 = emitc.div %arg0, %arg1 : (i32, i32) -> i32
-    %1 = emitc.div %arg2, %arg3 : (f32, f32) -> f32
-    ```
-    ```c++
-    // Code emitted for the operations above.
-    int32_t v5 = v1 / v2;
-    float v6 = v3 / v4;
+    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
+      }
+    }
+    // Class with a final speciferAdd commentMore actions
+    emitc.class final @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 FloatIntegerIndexOrOpaqueType, FloatIntegerIndexOrOpaqueType);
-  let results = (outs FloatIntegerIndexOrOpaqueType);
-}
+  let arguments = (ins SymbolNameAttr:$sym_name, UnitAttr:$final_specifier);
 
-def EmitC_ExpressionOp : EmitC_Op<"expression",
-      [HasOnlyGraphRegion, OpAsmOpInterface,
-       SingleBlockImplicitTerminator<"emitc::YieldOp">, NoRegionArguments]> {
-  let summary = "Expression operation";
-  let description = [{
-    The `emitc.expression` operation returns a single SSA value which is yielded by
-    its single-basic-block region. The operation doesn't take any arguments.
+  let regions = (region AnyRegion:$body);
 
-    As the operation is to be emitted as a C expression, the operations within
-    its body must form a single Def-Use tree of emitc ops whose result is
-    yielded by a terminating `emitc.yield`.
+  let extraClassDeclaration = [{
+    // Returns the body block containing class members and methods.
+    Block &getBlock();
+  }];
 
-    Example:
+  let hasCustomAssemblyFormat = 1;
+
+  let assemblyFormat =
+      [{ (`final` $final_specifier^)? $sym_name attr-dict-with-keyword $body }];
+}
+
+def EmitC_DeclareFuncOp : EmitC_Op<"declare_func", [
+  DeclareOpInterfaceMethods<SymbolUserOpInterface>
+]> {
+  let summary = "An operation to declare a function";
+  let description = [{
+    The `emitc.declare_func` operation allows to insert a function declaration for an
+    `emitc.func` at a specific position. The operation only requires the "callee"
+    of the `emitc.func` to be specified as an attribute.
+
+    Example:
+
+    ```mlir
+    emitc.declare_func @bar
+    emitc.func @foo(%arg0: i32) -> i32 {
+      %0 = emitc.call @bar(%arg0) : (i32) -> (i32)
+      emitc.return %0 : i32
+    }
+
+    emitc.func @bar(%arg0: i32) -> i32 {
+      emitc.return %arg0 : i32
+    }
+    ```
+
+    ```c++
+    // Code emitted for the operations above.
+    int32_t bar(int32_t v1);
+    int32_t foo(int32_t v1) {
+      int32_t v2 = bar(v1);
+      return v2;
+    }
+
+    int32_t bar(int32_t v1) {
+      return v1;
+    }
+    ```
+  }];
+  let arguments = (ins FlatSymbolRefAttr:$sym_name);
+  let assemblyFormat = [{
+    $sym_name attr-dict
+  }];
+}
+
+def EmitC_DivOp : EmitC_BinaryOp<"div", []> {
+  let summary = "Division operation";
+  let description = [{
+    With the `emitc.div` operation the arithmetic operator / (division) can
+    be applied.
+
+    Example:
+
+    ```mlir
+    // Custom form of the division operation.
+    %0 = emitc.div %arg0, %arg1 : (i32, i32) -> i32
+    %1 = emitc.div %arg2, %arg3 : (f32, f32) -> f32
+    ```
+    ```c++
+    // Code emitted for the operations above.
+    int32_t v5 = v1 / v2;
+    float v6 = v3 / v4;
+    ```
+  }];
+
+  let arguments = (ins FloatIntegerIndexOrOpaqueType, FloatIntegerIndexOrOpaqueType);
+  let results = (outs FloatIntegerIndexOrOpaqueType);
+}
+
+def EmitC_ExpressionOp : EmitC_Op<"expression",
+      [HasOnlyGraphRegion, OpAsmOpInterface,
+       SingleBlockImplicitTerminator<"emitc::YieldOp">, NoRegionArguments]> {
+  let summary = "Expression operation";
+  let description = [{
+    The `emitc.expression` operation returns a single SSA value which is yielded by
+    its single-basic-block region. The operation doesn't take any arguments.
+
+    As the operation is to be emitted as a C expression, the operations within
+    its body must form a single Def-Use tree of emitc ops whose result is
+    yielded by a terminating `emitc.yield`.
+
+    Example:
 
     ```mlir
     %r = emitc.expression : i32 {
@@ -519,6 +715,85 @@ def EmitC_ExpressionOp : EmitC_Op<"expression",
   }];
 }
 
+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>
+    // Example with an initial value:
+    emitc.field @fieldName0 : !emitc.array<1xf32> = dense<0.0>
+    // Example with an initial value and attributes:
+    emitc.field @fieldName0 : !emitc.array<1xf32> = dense<0.0> {
+      emitc.opaque = "input_tensor"}
+    ```
+  }];
+
+  let arguments = (ins SymbolNameAttr:$sym_name, TypeAttr:$type,
+      OptionalAttr<EmitC_OpaqueOrTypedAttr>:$initial_value);
+
+  let assemblyFormat = [{
+       $sym_name
+       `:` custom<EmitCFieldOpTypeAndInitialValue>($type, $initial_value)
+       attr-dict
+  }];
+
+  let hasVerifier = 1;
+}
+
+def EmitC_FileOp
+    : EmitC_Op<"file", [IsolatedFromAbove, NoRegionArguments, SymbolTable,
+                        OpAsmOpInterface]#GraphRegionNoTerminator.traits> {
+  let summary = "A file container operation";
+  let description = [{
+    A `file` represents a single C/C++ file.
+
+    `mlir-translate` ignores the body of all `emitc.file` ops
+    unless the `-file-id=id` flag is used. With that flag, all `emitc.file` ops
+    with matching id are emitted.
+
+    Example:
+
+    ```mlir
+    emitc.file "main" {
+      emitc.func @func_one() {
+        emitc.return
+      }
+    }
+    ```
+  }];
+
+  let arguments = (ins Builtin_StringAttr:$id);
+  let regions = (region SizedRegion<1>:$bodyRegion);
+
+  let assemblyFormat = "$id attr-dict-with-keyword $bodyRegion";
+  let builders = [OpBuilder<(ins CArg<"StringRef">:$id)>];
+  let extraClassDeclaration = [{
+    /// Construct a file op from the given location with a name.
+    static FileOp create(Location loc, StringRef name);
+
+    //===------------------------------------------------------------------===//
+    // OpAsmOpInterface Methods
+    //===------------------------------------------------------------------===//
+
+    /// EmitC ops in the body can omit their 'emitc.' prefix in the assembly.
+    static ::llvm::StringRef getDefaultDialect() {
+      return "emitc";
+    }
+  }];
+
+  // We need to ensure that the body region has a block;
+  // the auto-generated builders do not guarantee that.
+  let skipDefaultBuilders = 1;
+}
+
 def EmitC_ForOp : EmitC_Op<"for",
       [AllTypesMatch<["lowerBound", "upperBound", "step"]>,
        OpAsmOpInterface, SingleBlockImplicitTerminator<"emitc::YieldOp">,
@@ -589,172 +864,48 @@ def EmitC_ForOp : EmitC_Op<"for",
   let hasRegionVerifier = 1;
 }
 
-def EmitC_CallOp : EmitC_Op<"call",
-    [CallOpInterface, CExpressionInterface,
-     DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
-  let summary = "Call operation";
+def EmitC_FuncOp : EmitC_Op<"func", [
+  AutomaticAllocationScope,
+  FunctionOpInterface, IsolatedFromAbove, OpAsmOpInterface
+]> {
+  let summary = "An operation with a name containing a single `SSACFG` region";
   let description = [{
-    The `emitc.call` operation represents a direct call to an `emitc.func`
-    that is within the same symbol scope as the call. The operands and result type
-    of the call must match the specified function type. The callee is encoded as a
-    symbol reference attribute named "callee".
+    Operations within the function cannot implicitly capture values defined
+    outside of the function, i.e. Functions are `IsolatedFromAbove`. All
+    external references must use function arguments or attributes that establish
+    a symbolic connection (e.g. symbols referenced by name via a string
+    attribute like SymbolRefAttr). While the MLIR textual form provides a nice
+    inline syntax for function arguments, they are internally represented as
+    “block arguments” to the first block in the region.
+
+    Only dialect attribute names may be specified in the attribute dictionaries
+    for function arguments, results, or the function itself.
 
     Example:
 
     ```mlir
-    %2 = emitc.call @my_add(%0, %1) : (f32, f32) -> f32
-    ```
-  }];
-  let arguments = (ins 
-    FlatSymbolRefAttr:$callee,
-    Variadic<EmitCType>:$operands,
-    OptionalAttr<DictArrayAttr>:$arg_attrs,
-    OptionalAttr<DictArrayAttr>:$res_attrs
-  );
-
-  let results = (outs Variadic<EmitCType>);
-
-  let builders = [
-    OpBuilder<(ins "FuncOp":$callee, CArg<"ValueRange", "{}">:$operands), [{
-      $_state.addOperands(operands);
-      $_state.addAttribute("callee", SymbolRefAttr::get(callee));
-      $_state.addTypes(callee.getFunctionType().getResults());
-    }]>,
-    OpBuilder<(ins "SymbolRefAttr":$callee, "TypeRange":$results,
-      CArg<"ValueRange", "{}">:$operands), [{
-      $_state.addOperands(operands);
-      $_state.addAttribute("callee", callee);
-      $_state.addTypes(results);
-    }]>,
-    OpBuilder<(ins "StringAttr":$callee, "TypeRange":$results,
-      CArg<"ValueRange", "{}">:$operands), [{
-      build($_builder, $_state, SymbolRefAttr::get(callee), results, operands);
-    }]>,
-    OpBuilder<(ins "StringRef":$callee, "TypeRange":$results,
-      CArg<"ValueRange", "{}">:$operands), [{
-      build($_builder, $_state, StringAttr::get($_builder.getContext(), callee),
-            results, operands);
-    }]>];
-
-  let extraClassDeclaration = [{
-    FunctionType getCalleeType();
-
-    /// Get the argument operands to the called function.
-    operand_range getArgOperands() {
-      return {arg_operand_begin(), arg_operand_end()};
-    }
-
-    MutableOperandRange getArgOperandsMutable() {
-      return getOperandsMutable();
+    // A function with no results:
+    emitc.func @foo(%arg0 : i32) {
+      emitc.call_opaque "bar" (%arg0) : (i32) -> ()
+      emitc.return
     }
 
-    operand_iterator arg_operand_begin() { return operand_begin(); }
-    operand_iterator arg_operand_end() { return operand_end(); }
-
-    /// Return the callee of this operation.
-    CallInterfaceCallable getCallableForCallee() {
-      return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+    // A function with its argument as single result:
+    emitc.func @foo(%arg0 : i32) -> i32 {
+      emitc.return %arg0 : i32
     }
 
-    /// Set the callee for this operation.
-    void setCalleeFromCallable(CallInterfaceCallable callee) {
-      (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+    // A function with specifiers attribute:
+    emitc.func @example_specifiers_fn_attr() -> i32
+                attributes {specifiers = ["static","inline"]} {
+      %0 = emitc.call_opaque "foo" (): () -> i32
+      emitc.return %0 : i32
     }
 
-    bool hasSideEffects() {
-      return false;
-    }
-  }];
-
-  let assemblyFormat = [{
-    $callee `(` $operands `)` attr-dict `:` functional-type($operands, results)
-  }];
-}
-
-def EmitC_DeclareFuncOp : EmitC_Op<"declare_func", [
-  DeclareOpInterfaceMethods<SymbolUserOpInterface>
-]> {
-  let summary = "An operation to declare a function";
-  let description = [{
-    The `emitc.declare_func` operation allows to insert a function declaration for an
-    `emitc.func` at a specific position. The operation only requires the "callee"
-    of the `emitc.func` to be specified as an attribute.
-
-    Example:
-
-    ```mlir
-    emitc.declare_func @bar
-    emitc.func @foo(%arg0: i32) -> i32 {
-      %0 = emitc.call @bar(%arg0) : (i32) -> (i32)
-      emitc.return %0 : i32
-    }
-
-    emitc.func @bar(%arg0: i32) -> i32 {
-      emitc.return %arg0 : i32
-    }
-    ```
-
-    ```c++
-    // Code emitted for the operations above.
-    int32_t bar(int32_t v1);
-    int32_t foo(int32_t v1) {
-      int32_t v2 = bar(v1);
-      return v2;
-    }
-
-    int32_t bar(int32_t v1) {
-      return v1;
-    }
-    ```
-  }];
-  let arguments = (ins FlatSymbolRefAttr:$sym_name);
-  let assemblyFormat = [{
-    $sym_name attr-dict
-  }];
-}
-
-def EmitC_FuncOp : EmitC_Op<"func", [
-  AutomaticAllocationScope,
-  FunctionOpInterface, IsolatedFromAbove, OpAsmOpInterface
-]> {
-  let summary = "An operation with a name containing a single `SSACFG` region";
-  let description = [{
-    Operations within the function cannot implicitly capture values defined
-    outside of the function, i.e. Functions are `IsolatedFromAbove`. All
-    external references must use function arguments or attributes that establish
-    a symbolic connection (e.g. symbols referenced by name via a string
-    attribute like SymbolRefAttr). While the MLIR textual form provides a nice
-    inline syntax for function arguments, they are internally represented as
-    “block arguments” to the first block in the region.
-
-    Only dialect attribute names may be specified in the attribute dictionaries
-    for function arguments, results, or the function itself.
-
-    Example:
-
-    ```mlir
-    // A function with no results:
-    emitc.func @foo(%arg0 : i32) {
-      emitc.call_opaque "bar" (%arg0) : (i32) -> ()
-      emitc.return
-    }
-
-    // A function with its argument as single result:
-    emitc.func @foo(%arg0 : i32) -> i32 {
-      emitc.return %arg0 : i32
-    }
-
-    // A function with specifiers attribute:
-    emitc.func @example_specifiers_fn_attr() -> i32
-                attributes {specifiers = ["static","inline"]} {
-      %0 = emitc.call_opaque "foo" (): () -> i32
-      emitc.return %0 : i32
-    }
-
-    // An external function definition:
-    emitc.func private @extern_func(i32)
-                        attributes {specifiers = ["extern"]}
-    ```
+    // An external function definition:
+    emitc.func private @extern_func(i32)
+                        attributes {specifiers = ["extern"]}
+    ```
   }];
   let arguments = (ins SymbolNameAttr:$sym_name,
                        TypeAttrOf<FunctionType>:$function_type,
@@ -797,28 +948,162 @@ def EmitC_FuncOp : EmitC_Op<"func", [
   let hasVerifier = 1;
 }
 
-def EmitC_ReturnOp : EmitC_Op<"return", [Pure, HasParent<"FuncOp">,
-                                ReturnLike, Terminator]> {
-  let summary = "Function return operation";
+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.return` operation represents a return operation within a function.
-    The operation takes zero or exactly one operand and produces no results.
-    The operand number and type must match the signature of the function
-    that contains the operation.
+     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 EmitCType:$result);
+  let assemblyFormat = "$field_name `:` type($result) attr-dict";
+}
+
+def EmitC_GetGlobalOp : EmitC_Op<"get_global",
+    [Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
+  let summary = "Obtain access to a global variable";
+  let description = [{
+     The `emitc.get_global` operation retrieves the lvalue of a
+     named global variable. If the global variable is marked constant, assigning
+     to that lvalue is undefined.
+
+     Example:
+
+     ```mlir
+     %x = emitc.get_global @foo : !emitc.array<2xf32>
+     %y = emitc.get_global @bar : !emitc.lvalue<i32>
+     ```
+  }];
+
+  let arguments = (ins FlatSymbolRefAttr:$name);
+  let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>:$result);
+  let assemblyFormat = "$name `:` type($result) attr-dict";
+}
+
+def EmitC_GlobalOp : EmitC_Op<"global", [Symbol]> {
+  let summary = "A global variable";
+  let description = [{
+    The `emitc.global` operation declares or defines a named global variable.
+    The backing memory for the variable is allocated statically and described by
+    the variable's type, which must be an EmitC type.
+    Optionally, an `initial_value` can be provided.
+    Internal linkage can be specified using the `static_specifier` unit attribute
+    and external linkage can be specified using the `extern_specifier` unit attribute.
+    Note that the default linkage without those two keywords depends on whether
+    the target is C or C++ and whether the global variable is `const`.
+    The global variable can also be marked constant using the `const_specifier`
+    unit attribute. Writing to such constant global variables is
+    undefined.
+
+    The global variable can be accessed by using the `emitc.get_global` to
+    retrieve the value for the global variable.
 
     Example:
 
     ```mlir
-    emitc.func @foo() -> (i32) {
+    // Global variable with an initial value.
+    emitc.global @x : !emitc.array<2xf32> = dense<0.0>
+    // Global variable with an initial values.
+    emitc.global @x : !emitc.array<3xi32> = dense<[0, 1, 2]>
+    // Global variable with an opaque initial value.
+    emitc.global @x : !emitc.opaque<"char"> = #emitc.opaque<"CHAR_MIN">
+    // External global variable
+    emitc.global extern @x : !emitc.array<2xf32>
+    // Constant global variable with internal linkage
+    emitc.global static const @x : i32 = 0
+    ```
+  }];
+
+  let arguments = (ins SymbolNameAttr:$sym_name,
+                       TypeAttr:$type,
+                       OptionalAttr<EmitC_OpaqueOrTypedAttr>:$initial_value,
+                       UnitAttr:$extern_specifier,
+                       UnitAttr:$static_specifier,
+                       UnitAttr:$const_specifier);
+
+  let assemblyFormat = [{
+       (`extern` $extern_specifier^)?
+       (`static` $static_specifier^)?
+       (`const` $const_specifier^)?
+       $sym_name
+       `:` custom<EmitCGlobalOpTypeAndInitialValue>($type, $initial_value)
+       attr-dict
+  }];
+
+  let hasVerifier = 1;
+}
+
+def EmitC_IfOp : EmitC_Op<"if",
+    [DeclareOpInterfaceMethods<RegionBranchOpInterface, [
+    "getNumRegionInvocations", "getRegionInvocationBounds",
+    "getEntrySuccessorRegions"]>, OpAsmOpInterface, SingleBlock,
+    SingleBlockImplicitTerminator<"emitc::YieldOp">,
+    RecursiveMemoryEffects, NoRegionArguments]> {
+  let summary = "If-then-else operation";
+  let description = [{
+    The `emitc.if` operation represents an if-then-else construct for
+    conditionally executing two regions of code. The operand to an if operation
+    is a boolean value. For example:
+
+    ```mlir
+    emitc.if %b  {
+      ...
+    } else {
       ...
-      emitc.return %0 : i32
     }
     ```
+
+    The "then" region has exactly 1 block. The "else" region may have 0 or 1
+    blocks. The blocks are always terminated with `emitc.yield`, which can be
+    left out to be inserted implicitly. This operation doesn't produce any
+    results.
   }];
-  let arguments = (ins Optional<EmitCType>:$operand);
+  let arguments = (ins I1:$condition);
+  let results = (outs);
+  let regions = (region SizedRegion<1>:$thenRegion,
+                        MaxSizedRegion<1>:$elseRegion);
 
-  let assemblyFormat = "attr-dict ($operand^ `:` type($operand))?";
-  let hasVerifier = 1;
+  let skipDefaultBuilders = 1;
+  let builders = [
+    OpBuilder<(ins "Value":$cond)>,
+    OpBuilder<(ins "Value":$cond, "bool":$addThenBlock, "bool":$addElseBlock)>,
+    OpBuilder<(ins "Value":$cond, "bool":$withElseRegion)>,
+    OpBuilder<(ins "Value":$cond,
+      CArg<"function_ref<void(OpBuilder &, Location)>",
+           "buildTerminatedBody">:$thenBuilder,
+      CArg<"function_ref<void(OpBuilder &, Location)>",
+           "nullptr">:$elseBuilder)>,
+  ];
+
+  let extraClassDeclaration = [{
+    OpBuilder getThenBodyBuilder(OpBuilder::Listener *listener = nullptr) {
+      Block* body = getBody(0);
+      return OpBuilder::atBlockEnd(body, listener);
+    }
+    OpBuilder getElseBodyBuilder(OpBuilder::Listener *listener = nullptr) {
+      Block* body = getBody(1);
+      return OpBuilder::atBlockEnd(body, listener);
+    }
+
+    //===------------------------------------------------------------------===//
+    // OpAsmOpInterface Methods
+    //===------------------------------------------------------------------===//
+
+    /// EmitC ops in the body can omit their 'emitc.' prefix in the assembly.
+    static ::llvm::StringRef getDefaultDialect() {
+      return "emitc";
+    }
+  }];
+  let hasCustomAssemblyFormat = 1;
 }
 
 def EmitC_IncludeOp
@@ -876,69 +1161,6 @@ def EmitC_LiteralOp : EmitC_Op<"literal", [Pure]> {
   let assemblyFormat = "$value attr-dict `:` type($result)";
 }
 
-def EmitC_LogicalAndOp : EmitC_BinaryOp<"logical_and", []> {
-  let summary = "Logical and operation";
-  let description = [{
-    With the `emitc.logical_and` operation the logical operator && (and) can
-    be applied.
-
-    Example:
-
-    ```mlir
-    %0 = emitc.logical_and %arg0, %arg1 : i32, i32
-    ```
-    ```c++
-    // Code emitted for the operation above.
-    bool v3 = v1 && v2;
-    ```
-  }];
-
-  let results = (outs I1);
-  let assemblyFormat = "operands attr-dict `:` type(operands)";
-}
-
-def EmitC_LogicalNotOp : EmitC_UnaryOp<"logical_not", []> {
-  let summary = "Logical not operation";
-  let description = [{
-    With the `emitc.logical_not` operation the logical operator ! (negation) can
-    be applied.
-
-    Example:
-
-    ```mlir
-    %0 = emitc.logical_not %arg0 : i32
-    ```
-    ```c++
-    // Code emitted for the operation above.
-    bool v2 = !v1;
-    ```
-  }];
-
-  let results = (outs I1);
-  let assemblyFormat = "operands attr-dict `:` type(operands)";
-}
-
-def EmitC_LogicalOrOp : EmitC_BinaryOp<"logical_or", []> {
-  let summary = "Logical or operation";
-  let description = [{
-    With the `emitc.logical_or` operation the logical operator || (inclusive or)
-    can be applied.
-
-    Example:
-
-    ```mlir
-    %0 = emitc.logical_or %arg0, %arg1 : i32, i32
-    ```
-    ```c++
-    // Code emitted for the operation above.
-    bool v3 = v1 || v2;
-    ```
-  }];
-
-  let results = (outs I1);
-  let assemblyFormat = "operands attr-dict `:` type(operands)";
-}
-
 def EmitC_LoadOp : EmitC_Op<"load", [CExpressionInterface,
   TypesMatchWith<"result type matches value type of 'operand'",
                   "operand", "result",
@@ -946,8 +1168,8 @@ def EmitC_LoadOp : EmitC_Op<"load", [CExpressionInterface,
 ]> {
   let summary = "Load an lvalue into an SSA value.";
   let description = [{
-    This operation loads the content of a modifiable lvalue into an SSA value. 
-    Modifications of the lvalue executed after the load are not observable on 
+    This operation loads the content of a modifiable lvalue into an SSA value.
+    Modifications of the lvalue executed after the load are not observable on
     the produced value.
 
     Example:
@@ -961,83 +1183,74 @@ def EmitC_LoadOp : EmitC_Op<"load", [CExpressionInterface,
     ```
   }];
 
-  let arguments = (ins 
+  let arguments = (ins
       Res<EmitC_LValueType, "", [MemRead<DefaultResource, 0, FullEffect>]>:$operand);
   let results = (outs AnyType:$result);
 
-  let assemblyFormat = "$operand attr-dict `:` type($operand)"; 
+  let assemblyFormat = "$operand attr-dict `:` type($operand)";
 }
 
-def EmitC_MulOp : EmitC_BinaryOp<"mul", []> {
-  let summary = "Multiplication operation";
+def EmitC_LogicalAndOp : EmitC_BinaryOp<"logical_and", []> {
+  let summary = "Logical and operation";
   let description = [{
-    With the `emitc.mul` operation the arithmetic operator * (multiplication) can
+    With the `emitc.logical_and` operation the logical operator && (and) can
     be applied.
 
     Example:
 
     ```mlir
-    // Custom form of the multiplication operation.
-    %0 = emitc.mul %arg0, %arg1 : (i32, i32) -> i32
-    %1 = emitc.mul %arg2, %arg3 : (f32, f32) -> f32
+    %0 = emitc.logical_and %arg0, %arg1 : i32, i32
     ```
     ```c++
-    // Code emitted for the operations above.
-    int32_t v5 = v1 * v2;
-    float v6 = v3 * v4;
+    // Code emitted for the operation above.
+    bool v3 = v1 && v2;
     ```
   }];
 
-  let arguments = (ins FloatIntegerIndexOrOpaqueType, FloatIntegerIndexOrOpaqueType);
-  let results = (outs FloatIntegerIndexOrOpaqueType);
+  let results = (outs I1);
+  let assemblyFormat = "operands attr-dict `:` type(operands)";
 }
 
-def EmitC_RemOp : EmitC_BinaryOp<"rem", []> {
-  let summary = "Remainder operation";
+def EmitC_LogicalNotOp : EmitC_UnaryOp<"logical_not", []> {
+  let summary = "Logical not operation";
   let description = [{
-    With the `emitc.rem` operation the arithmetic operator % (remainder) can
+    With the `emitc.logical_not` operation the logical operator ! (negation) can
     be applied.
 
     Example:
 
     ```mlir
-    // Custom form of the remainder operation.
-    %0 = emitc.rem %arg0, %arg1 : (i32, i32) -> i32
+    %0 = emitc.logical_not %arg0 : i32
     ```
     ```c++
     // Code emitted for the operation above.
-    int32_t v5 = v1 % v2;
+    bool v2 = !v1;
     ```
   }];
 
-  let arguments = (ins IntegerIndexOrOpaqueType, IntegerIndexOrOpaqueType);
-  let results = (outs IntegerIndexOrOpaqueType);
+  let results = (outs I1);
+  let assemblyFormat = "operands attr-dict `:` type(operands)";
 }
 
-def EmitC_SubOp : EmitC_BinaryOp<"sub", []> {
-  let summary = "Subtraction operation";
+def EmitC_LogicalOrOp : EmitC_BinaryOp<"logical_or", []> {
+  let summary = "Logical or operation";
   let description = [{
-    With the `emitc.sub` operation the arithmetic operator - (subtraction) can
-    be applied.
+    With the `emitc.logical_or` operation the logical operator || (inclusive or)
+    can be applied.
 
     Example:
 
     ```mlir
-    // Custom form of the substraction operation.
-    %0 = emitc.sub %arg0, %arg1 : (i32, i32) -> i32
-    %1 = emitc.sub %arg2, %arg3 : (!emitc.ptr<f32>, i32) -> !emitc.ptr<f32>
-    %2 = emitc.sub %arg4, %arg5 : (!emitc.ptr<i32>, !emitc.ptr<i32>)
-        -> !emitc.ptrdiff_t
+    %0 = emitc.logical_or %arg0, %arg1 : i32, i32
     ```
     ```c++
-    // Code emitted for the operations above.
-    int32_t v7 = v1 - v2;
-    float* v8 = v3 - v4;
-    ptrdiff_t v9 = v5 - v6;
+    // Code emitted for the operation above.
+    bool v3 = v1 || v2;
     ```
   }];
 
-  let hasVerifier = 1;
+  let results = (outs I1);
+  let assemblyFormat = "operands attr-dict `:` type(operands)";
 }
 
 def EmitC_MemberOp : EmitC_Op<"member"> {
@@ -1083,371 +1296,100 @@ def EmitC_MemberOfPtrOp : EmitC_Op<"member_of_ptr"> {
   let results = (outs EmitC_LValueOf<[EmitCType]>);
 }
 
-def EmitC_ConditionalOp : EmitC_Op<"conditional",
-    [AllTypesMatch<["true_value", "false_value", "result"]>, CExpressionInterface]> {
-  let summary = "Conditional (ternary) operation";
+def EmitC_MulOp : EmitC_BinaryOp<"mul", []> {
+  let summary = "Multiplication operation";
   let description = [{
-    With the `emitc.conditional` operation the ternary conditional operator can
+    With the `emitc.mul` operation the arithmetic operator * (multiplication) can
     be applied.
 
     Example:
 
     ```mlir
-    %0 = emitc.cmp gt, %arg0, %arg1 : (i32, i32) -> i1
-
-    %c0 = "emitc.constant"() {value = 10 : i32} : () -> i32
-    %c1 = "emitc.constant"() {value = 11 : i32} : () -> i32
-
-    %1 = emitc.conditional %0, %c0, %c1 : i32
+    // Custom form of the multiplication operation.
+    %0 = emitc.mul %arg0, %arg1 : (i32, i32) -> i32
+    %1 = emitc.mul %arg2, %arg3 : (f32, f32) -> f32
     ```
     ```c++
     // Code emitted for the operations above.
-    bool v3 = v1 > v2;
-    int32_t v4 = 10;
-    int32_t v5 = 11;
-    int32_t v6 = v3 ? v4 : v5;
-    ```
-  }];
-  let arguments = (ins I1:$condition, EmitCType:$true_value, EmitCType:$false_value);
-  let results = (outs EmitCType:$result);
-  let assemblyFormat = "operands attr-dict `:` type($result)";
-
-  let extraClassDeclaration = [{
-    bool hasSideEffects() {
-      return false;
-    }
-  }];
-}
-
-def EmitC_UnaryMinusOp : EmitC_UnaryOp<"unary_minus", []> {
-  let summary = "Unary minus operation";
-  let description = [{
-    With the `emitc.unary_minus` operation the unary operator - (minus) can be
-    applied.
-
-    Example:
-
-    ```mlir
-    %0 = emitc.unary_minus %arg0 : (i32) -> i32
-    ```
-    ```c++
-    // Code emitted for the operation above.
-    int32_t v2 = -v1;
-    ```
-  }];
-}
-
-def EmitC_UnaryPlusOp : EmitC_UnaryOp<"unary_plus", []> {
-  let summary = "Unary plus operation";
-  let description = [{
-    With the `emitc.unary_plus` operation the unary operator + (plus) can be
-    applied.
-
-    Example:
-
-    ```mlir
-    %0 = emitc.unary_plus %arg0 : (i32) -> i32
-    ```
-    ```c++
-    // Code emitted for the operation above.
-    int32_t v2 = +v1;
-    ```
-  }];
-}
-
-def EmitC_VariableOp : EmitC_Op<"variable", []> {
-  let summary = "Variable operation";
-  let description = [{
-    The `emitc.variable` operation produces an SSA value equal to some value
-    specified by an attribute. This can be used to form simple integer and
-    floating point variables, as well as more exotic things like tensor
-    variables. The `emitc.variable` operation also supports the EmitC opaque
-    attribute and the EmitC opaque type. If further supports the EmitC
-    pointer type, whereas folding is not supported.
-    The `emitc.variable` is emitted as a C/C++ local variable.
-
-    Example:
-
-    ```mlir
-    // Integer variable
-    %0 = "emitc.variable"(){value = 42 : i32} : () -> !emitc.lvalue<i32>
-
-    // Variable emitted as `int32_t* = NULL;`
-    %1 = "emitc.variable"() {value = #emitc.opaque<"NULL">} 
-      : () -> !emitc.lvalue<!emitc.ptr<!emitc.opaque<"int32_t">>>
-    ```
-
-    Since folding is not supported, it can be used with pointers.
-    As an example, it is valid to create pointers to `variable` operations
-    by using `apply` operations and pass these to a `call` operation.
-    ```mlir
-    %0 = "emitc.variable"() {value = 0 : i32} : () -> !emitc.lvalue<i32>
-    %1 = "emitc.variable"() {value = 0 : i32} : () -> !emitc.lvalue<i32>
-    %2 = emitc.apply "&"(%0) : (!emitc.lvalue<i32>) -> !emitc.ptr<i32>
-    %3 = emitc.apply "&"(%1) : (!emitc.lvalue<i32>) -> !emitc.ptr<i32>
-    emitc.call_opaque "write"(%2, %3)
-      : (!emitc.ptr<i32>, !emitc.ptr<i32>) -> ()
-    ```
-  }];
-
-  let arguments = (ins EmitC_OpaqueOrTypedAttr:$value);
-  let results = (outs Res<AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>, "",
-                          [MemAlloc<DefaultResource, 0, FullEffect>]>);
-
-  let hasVerifier = 1;
-}
-
-def EmitC_GlobalOp : EmitC_Op<"global", [Symbol]> {
-  let summary = "A global variable";
-  let description = [{
-    The `emitc.global` operation declares or defines a named global variable.
-    The backing memory for the variable is allocated statically and described by
-    the variable's type, which must be an EmitC type.
-    Optionally, an `initial_value` can be provided.
-    Internal linkage can be specified using the `static_specifier` unit attribute
-    and external linkage can be specified using the `extern_specifier` unit attribute.
-    Note that the default linkage without those two keywords depends on whether
-    the target is C or C++ and whether the global variable is `const`.
-    The global variable can also be marked constant using the `const_specifier`
-    unit attribute. Writing to such constant global variables is
-    undefined.
-
-    The global variable can be accessed by using the `emitc.get_global` to
-    retrieve the value for the global variable.
-
-    Example:
-
-    ```mlir
-    // Global variable with an initial value.
-    emitc.global @x : !emitc.array<2xf32> = dense<0.0>
-    // Global variable with an initial values.
-    emitc.global @x : !emitc.array<3xi32> = dense<[0, 1, 2]>
-    // Global variable with an opaque initial value.
-    emitc.global @x : !emitc.opaque<"char"> = #emitc.opaque<"CHAR_MIN">
-    // External global variable
-    emitc.global extern @x : !emitc.array<2xf32>
-    // Constant global variable with internal linkage
-    emitc.global static const @x : i32 = 0
+    int32_t v5 = v1 * v2;
+    float v6 = v3 * v4;
     ```
   }];
 
-  let arguments = (ins SymbolNameAttr:$sym_name,
-                       TypeAttr:$type,
-                       OptionalAttr<EmitC_OpaqueOrTypedAttr>:$initial_value,
-                       UnitAttr:$extern_specifier,
-                       UnitAttr:$static_specifier,
-                       UnitAttr:$const_specifier);
-
-  let assemblyFormat = [{
-       (`extern` $extern_specifier^)?
-       (`static` $static_specifier^)?
-       (`const` $const_specifier^)?
-       $sym_name
-       `:` custom<EmitCGlobalOpTypeAndInitialValue>($type, $initial_value)
-       attr-dict
-  }];
-
-  let hasVerifier = 1;
-}
-
-def EmitC_GetGlobalOp : EmitC_Op<"get_global",
-    [Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
-  let summary = "Obtain access to a global variable";
-  let description = [{
-     The `emitc.get_global` operation retrieves the lvalue of a
-     named global variable. If the global variable is marked constant, assigning
-     to that lvalue is undefined.
-
-     Example:
-
-     ```mlir
-     %x = emitc.get_global @foo : !emitc.array<2xf32>
-     %y = emitc.get_global @bar : !emitc.lvalue<i32>
-     ```
-  }];
-
-  let arguments = (ins FlatSymbolRefAttr:$name);
-  let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>:$result);
-  let assemblyFormat = "$name `:` type($result) attr-dict";
+  let arguments = (ins FloatIntegerIndexOrOpaqueType, FloatIntegerIndexOrOpaqueType);
+  let results = (outs FloatIntegerIndexOrOpaqueType);
 }
 
-def EmitC_VerbatimOp : EmitC_Op<"verbatim"> {
-  let summary = "Verbatim operation";
+def EmitC_RemOp : EmitC_BinaryOp<"rem", []> {
+  let summary = "Remainder operation";
   let description = [{
-    The `emitc.verbatim` operation produces no results and the value is emitted as is
-    followed by a line break  ('\n' character) during translation.
-
-    Note: Use with caution. This operation can have arbitrary effects on the
-    semantics of the emitted code. Use semantically more meaningful operations
-    whenever possible. Additionally this op is *NOT* intended to be used to
-    inject large snippets of code.
-
-    This operation can be used in situations where a more suitable operation is
-    not yet implemented in the dialect or where preprocessor directives
-    interfere with the structure of the code. One example of this is to declare
-    the linkage of external symbols to make the generated code usable in both C
-    and C++ contexts:
-
-    ```c++
-    #ifdef __cplusplus
-    extern "C" {
-    #endif
-
-    ...
-    
-    #ifdef __cplusplus
-    }
-    #endif
-    ```
-
-    If the `emitc.verbatim` op has operands, then the `value` is interpreted as
-    format string, where `{}` is a placeholder for an operand in their order.
-    For example, `emitc.verbatim "#pragma my src={} dst={}" %src, %dest : i32, i32`
-    would be emitted as `#pragma my src=a dst=b` if `%src` became `a` and
-    `%dest` became `b` in the C code.
-    `{{` in the format string is interpreted as a single `{` and doesn't introduce
-    a placeholder.
+    With the `emitc.rem` operation the arithmetic operator % (remainder) can
+    be applied.
 
     Example:
 
     ```mlir
-    emitc.verbatim "typedef float f32;"
-    emitc.verbatim "#pragma my var={} property" args %arg : f32
+    // Custom form of the remainder operation.
+    %0 = emitc.rem %arg0, %arg1 : (i32, i32) -> i32
     ```
     ```c++
     // Code emitted for the operation above.
-    typedef float f32;
-    #pragma my var=v1 property
-    ```
-  }];
-
-  let extraClassDeclaration = [{
-    FailureOr<SmallVector<::mlir::emitc::ReplacementItem>> parseFormatString();
-  }];
-
-  let arguments = (ins StrAttr:$value, Variadic<AnyTypeOf<[EmitCType, EmitC_LValueType]>>:$fmtArgs);
-
-  let builders = [OpBuilder<(ins "::mlir::StringAttr":$value),
-                            [{ build($_builder, $_state, value, {}); }]>];
-  let builders = [OpBuilder<(ins "::llvm::StringRef":$value),
-                            [{ build($_builder, $_state, value, {}); }]>];
-  let hasVerifier = 1;
-  let assemblyFormat =
-      "$value (`args` $fmtArgs^ `:` type($fmtArgs))? attr-dict";
-}
-
-def EmitC_AssignOp : EmitC_Op<"assign", []> {
-  let summary = "Assign operation";
-  let description = [{
-    The `emitc.assign` operation stores an SSA value to the location designated by an
-    EmitC variable. This operation doesn't return any value. The assigned value
-    must be of the same type as the variable being assigned. The operation is
-    emitted as a C/C++ '=' operator.
-
-    Example:
-
-    ```mlir
-    // Integer variable
-    %0 = "emitc.variable"(){value = 42 : i32} : () -> !emitc.lvalue<i32>
-    %1 = emitc.call_opaque "foo"() : () -> (i32)
-
-    // Assign emitted as `... = ...;`
-    "emitc.assign"(%0, %1) : (!emitc.lvalue<i32>, i32) -> ()
+    int32_t v5 = v1 % v2;
     ```
   }];
 
-  let arguments = (ins 
-      Res<EmitC_LValueType, "", [MemWrite<DefaultResource, 1, FullEffect>]>:$var,
-      EmitCType:$value);
-  let results = (outs);
-
-  let hasVerifier = 1;
-  let assemblyFormat = "$value `:` type($value) `to` $var `:` type($var) attr-dict";
-}
-
-def EmitC_YieldOp : EmitC_Op<"yield",
-      [Pure, Terminator, ParentOneOf<["ExpressionOp", "IfOp", "ForOp", "SwitchOp"]>]> {
-  let summary = "Block termination operation";
-  let description = [{
-    The `emitc.yield` terminates its parent EmitC op's region, optionally yielding
-    an SSA value. The semantics of how the values are yielded is defined by the
-    parent operation.
-    If `emitc.yield` has an operand, the operand must match the parent operation's
-    result. If the parent operation defines no values, then the `emitc.yield`
-    may be left out in the custom syntax and the builders will insert one
-    implicitly. Otherwise, it has to be present in the syntax to indicate which
-    value is yielded.
-  }];
-
-  let arguments = (ins Optional<EmitCType>:$result);
-  let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>];
-
-  let hasVerifier = 1;
-  let assemblyFormat = [{ attr-dict ($result^ `:` type($result))? }];
+  let arguments = (ins IntegerIndexOrOpaqueType, IntegerIndexOrOpaqueType);
+  let results = (outs IntegerIndexOrOpaqueType);
 }
 
-def EmitC_IfOp : EmitC_Op<"if",
-    [DeclareOpInterfaceMethods<RegionBranchOpInterface, [
-    "getNumRegionInvocations", "getRegionInvocationBounds",
-    "getEntrySuccessorRegions"]>, OpAsmOpInterface, SingleBlock,
-    SingleBlockImplicitTerminator<"emitc::YieldOp">,
-    RecursiveMemoryEffects, NoRegionArguments]> {
-  let summary = "If-then-else operation";
+def EmitC_ReturnOp : EmitC_Op<"return", [Pure, HasParent<"FuncOp">,
+                                ReturnLike, Terminator]> {
+  let summary = "Function return operation";
   let description = [{
-    The `emitc.if` operation represents an if-then-else construct for
-    conditionally executing two regions of code. The operand to an if operation
-    is a boolean value. For example:
-
-    ```mlir
-    emitc.if %b  {
-      ...
-    } else {
-      ...
-    }
-    ```
-
-    The "then" region has exactly 1 block. The "else" region may have 0 or 1
-    blocks. The blocks are always terminated with `emitc.yield`, which can be
-    left out to be inserted implicitly. This operation doesn't produce any
-    results.
-  }];
-  let arguments = (ins I1:$condition);
-  let results = (outs);
-  let regions = (region SizedRegion<1>:$thenRegion,
-                        MaxSizedRegion<1>:$elseRegion);
-
-  let skipDefaultBuilders = 1;
-  let builders = [
-    OpBuilder<(ins "Value":$cond)>,
-    OpBuilder<(ins "Value":$cond, "bool":$addThenBlock, "bool":$addElseBlock)>,
-    OpBuilder<(ins "Value":$cond, "bool":$withElseRegion)>,
-    OpBuilder<(ins "Value":$cond,
-      CArg<"function_ref<void(OpBuilder &, Location)>",
-           "buildTerminatedBody">:$thenBuilder,
-      CArg<"function_ref<void(OpBuilder &, Location)>",
-           "nullptr">:$elseBuilder)>,
-  ];
+    The `emitc.return` operation represents a return operation within a function.
+    The operation takes zero or exactly one operand and produces no results.
+    The operand number and type must match the signature of the function
+    that contains the operation.
 
-  let extraClassDeclaration = [{
-    OpBuilder getThenBodyBuilder(OpBuilder::Listener *listener = nullptr) {
-      Block* body = getBody(0);
-      return OpBuilder::atBlockEnd(body, listener);
-    }
-    OpBuilder getElseBodyBuilder(OpBuilder::Listener *listener = nullptr) {
-      Block* body = getBody(1);
-      return OpBuilder::atBlockEnd(body, listener);
+    Example:
+
+    ```mlir
+    emitc.func @foo() -> (i32) {
+      ...
+      emitc.return %0 : i32
     }
+    ```
+  }];
+  let arguments = (ins Optional<EmitCType>:$operand);
 
-    //===------------------------------------------------------------------===//
-    // OpAsmOpInterface Methods
-    //===------------------------------------------------------------------===//
+  let assemblyFormat = "attr-dict ($operand^ `:` type($operand))?";
+  let hasVerifier = 1;
+}
 
-    /// EmitC ops in the body can omit their 'emitc.' prefix in the assembly.
-    static ::llvm::StringRef getDefaultDialect() {
-      return "emitc";
-    }
+def EmitC_SubOp : EmitC_BinaryOp<"sub", []> {
+  let summary = "Subtraction operation";
+  let description = [{
+    With the `emitc.sub` operation the arithmetic operator - (subtraction) can
+    be applied.
+
+    Example:
+
+    ```mlir
+    // Custom form of the substraction operation.
+    %0 = emitc.sub %arg0, %arg1 : (i32, i32) -> i32
+    %1 = emitc.sub %arg2, %arg3 : (!emitc.ptr<f32>, i32) -> !emitc.ptr<f32>
+    %2 = emitc.sub %arg4, %arg5 : (!emitc.ptr<i32>, !emitc.ptr<i32>)
+        -> !emitc.ptrdiff_t
+    ```
+    ```c++
+    // Code emitted for the operations above.
+    int32_t v7 = v1 - v2;
+    float* v8 = v3 - v4;
+    ptrdiff_t v9 = v5 - v6;
+    ```
   }];
-  let hasCustomAssemblyFormat = 1;
+
+  let hasVerifier = 1;
 }
 
 def EmitC_SubscriptOp : EmitC_Op<"subscript", []> {
@@ -1593,110 +1535,168 @@ def EmitC_SwitchOp : EmitC_Op<"switch", [RecursiveMemoryEffects,
   let hasVerifier = 1;
 }
 
-def EmitC_ClassOp
-    : EmitC_Op<"class", [AutomaticAllocationScope, IsolatedFromAbove,
-                         OpAsmOpInterface, SymbolTable,
-                         Symbol]#GraphRegionNoTerminator.traits> {
-  let summary =
-      "Represents a C++ class definition, encapsulating fields and methods.";
+def EmitC_UnaryMinusOp : EmitC_UnaryOp<"unary_minus", []> {
+  let summary = "Unary minus operation";
+  let description = [{
+    With the `emitc.unary_minus` operation the unary operator - (minus) can be
+    applied.
+
+    Example:
+
+    ```mlir
+    %0 = emitc.unary_minus %arg0 : (i32) -> i32
+    ```
+    ```c++
+    // Code emitted for the operation above.
+    int32_t v2 = -v1;
+    ```
+  }];
+}
 
+def EmitC_UnaryPlusOp : EmitC_UnaryOp<"unary_plus", []> {
+  let summary = "Unary plus operation";
   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.
+    With the `emitc.unary_plus` operation the unary operator + (plus) can be
+    applied.
 
     Example:
 
     ```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
-      }
-    }
-    // Class with a final speciferAdd commentMore actions
-    emitc.class final @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
-      }
-    }
+    %0 = emitc.unary_plus %arg0 : (i32) -> i32
+    ```
+    ```c++
+    // Code emitted for the operation above.
+    int32_t v2 = +v1;
     ```
   }];
+}
 
-  let arguments = (ins SymbolNameAttr:$sym_name, UnitAttr:$final_specifier);
+def EmitC_VariableOp : EmitC_Op<"variable", []> {
+  let summary = "Variable operation";
+  let description = [{
+    The `emitc.variable` operation produces an SSA value equal to some value
+    specified by an attribute. This can be used to form simple integer and
+    floating point variables, as well as more exotic things like tensor
+    variables. The `emitc.variable` operation also supports the EmitC opaque
+    attribute and the EmitC opaque type. If further supports the EmitC
+    pointer type, whereas folding is not supported.
+    The `emitc.variable` is emitted as a C/C++ local variable.
 
-  let regions = (region AnyRegion:$body);
+    Example:
 
-  let extraClassDeclaration = [{
-    // Returns the body block containing class members and methods.
-    Block &getBlock();
+    ```mlir
+    // Integer variable
+    %0 = "emitc.variable"(){value = 42 : i32} : () -> !emitc.lvalue<i32>
+
+    // Variable emitted as `int32_t* = NULL;`
+    %1 = "emitc.variable"() {value = #emitc.opaque<"NULL">} 
+      : () -> !emitc.lvalue<!emitc.ptr<!emitc.opaque<"int32_t">>>
+    ```
+
+    Since folding is not supported, it can be used with pointers.
+    As an example, it is valid to create pointers to `variable` operations
+    by using `apply` operations and pass these to a `call` operation.
+    ```mlir
+    %0 = "emitc.variable"() {value = 0 : i32} : () -> !emitc.lvalue<i32>
+    %1 = "emitc.variable"() {value = 0 : i32} : () -> !emitc.lvalue<i32>
+    %2 = emitc.apply "&"(%0) : (!emitc.lvalue<i32>) -> !emitc.ptr<i32>
+    %3 = emitc.apply "&"(%1) : (!emitc.lvalue<i32>) -> !emitc.ptr<i32>
+    emitc.call_opaque "write"(%2, %3)
+      : (!emitc.ptr<i32>, !emitc.ptr<i32>) -> ()
+    ```
   }];
 
-  let hasCustomAssemblyFormat = 1;
+  let arguments = (ins EmitC_OpaqueOrTypedAttr:$value);
+  let results = (outs Res<AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>, "",
+                          [MemAlloc<DefaultResource, 0, FullEffect>]>);
 
-  let assemblyFormat =
-      [{ (`final` $final_specifier^)? $sym_name attr-dict-with-keyword $body }];
+  let hasVerifier = 1;
 }
 
-def EmitC_FieldOp : EmitC_Op<"field", [Symbol]> {
-  let summary = "A field within a class";
+def EmitC_VerbatimOp : EmitC_Op<"verbatim"> {
+  let summary = "Verbatim operation";
   let description = [{
-    The `emitc.field` operation declares a named field within an `emitc.class`
-    operation. The field's type must be an EmitC type. 
+    The `emitc.verbatim` operation produces no results and the value is emitted as is
+    followed by a line break  ('\n' character) during translation.
+
+    Note: Use with caution. This operation can have arbitrary effects on the
+    semantics of the emitted code. Use semantically more meaningful operations
+    whenever possible. Additionally this op is *NOT* intended to be used to
+    inject large snippets of code.
+
+    This operation can be used in situations where a more suitable operation is
+    not yet implemented in the dialect or where preprocessor directives
+    interfere with the structure of the code. One example of this is to declare
+    the linkage of external symbols to make the generated code usable in both C
+    and C++ contexts:
+
+    ```c++
+    #ifdef __cplusplus
+    extern "C" {
+    #endif
+
+    ...
+
+    #ifdef __cplusplus
+    }
+    #endif
+    ```
+
+    If the `emitc.verbatim` op has operands, then the `value` is interpreted as
+    format string, where `{}` is a placeholder for an operand in their order.
+    For example, `emitc.verbatim "#pragma my src={} dst={}" %src, %dest : i32, i32`
+    would be emitted as `#pragma my src=a dst=b` if `%src` became `a` and
+    `%dest` became `b` in the C code.
+    `{{` in the format string is interpreted as a single `{` and doesn't introduce
+    a placeholder.
 
     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>
-    // Example with an initial value:
-    emitc.field @fieldName0 : !emitc.array<1xf32> = dense<0.0>
-    // Example with an initial value and attributes:
-    emitc.field @fieldName0 : !emitc.array<1xf32> = dense<0.0> {
-      emitc.opaque = "input_tensor"}
+    emitc.verbatim "typedef float f32;"
+    emitc.verbatim "#pragma my var={} property" args %arg : f32
+    ```
+    ```c++
+    // Code emitted for the operation above.
+    typedef float f32;
+    #pragma my var=v1 property
     ```
   }];
 
-  let arguments = (ins SymbolNameAttr:$sym_name, TypeAttr:$type,
-      OptionalAttr<EmitC_OpaqueOrTypedAttr>:$initial_value);
-
-  let assemblyFormat = [{
-       $sym_name
-       `:` custom<EmitCFieldOpTypeAndInitialValue>($type, $initial_value)
-       attr-dict
+  let extraClassDeclaration = [{
+    FailureOr<SmallVector<::mlir::emitc::ReplacementItem>> parseFormatString();
   }];
 
+  let arguments = (ins StrAttr:$value, Variadic<AnyTypeOf<[EmitCType, EmitC_LValueType]>>:$fmtArgs);
+
+  let builders = [OpBuilder<(ins "::mlir::StringAttr":$value),
+                            [{ build($_builder, $_state, value, {}); }]>];
+  let builders = [OpBuilder<(ins "::llvm::StringRef":$value),
+                            [{ build($_builder, $_state, value, {}); }]>];
   let hasVerifier = 1;
+  let assemblyFormat =
+      "$value (`args` $fmtArgs^ `:` type($fmtArgs))? attr-dict";
 }
 
-def EmitC_GetFieldOp
-    : EmitC_Op<"get_field", [Pure, DeclareOpInterfaceMethods<
-                                       SymbolUserOpInterface>]> {
-  let summary = "Obtain access to a field within a class instance";
+def EmitC_YieldOp : EmitC_Op<"yield",
+      [Pure, Terminator, ParentOneOf<["ExpressionOp", "IfOp", "ForOp", "SwitchOp"]>]> {
+  let summary = "Block termination operation";
   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>
-     ```
+    The `emitc.yield` terminates its parent EmitC op's region, optionally yielding
+    an SSA value. The semantics of how the values are yielded is defined by the
+    parent operation.
+    If `emitc.yield` has an operand, the operand must match the parent operation's
+    result. If the parent operation defines no values, then the `emitc.yield`
+    may be left out in the custom syntax and the builders will insert one
+    implicitly. Otherwise, it has to be present in the syntax to indicate which
+    value is yielded.
   }];
 
-  let arguments = (ins FlatSymbolRefAttr:$field_name);
-  let results = (outs EmitCType:$result);
-  let assemblyFormat = "$field_name `:` type($result) attr-dict";
+  let arguments = (ins Optional<EmitCType>:$result);
+  let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>];
+
+  let hasVerifier = 1;
+  let assemblyFormat = [{ attr-dict ($result^ `:` type($result))? }];
 }
 
 #endif // MLIR_DIALECT_EMITC_IR_EMITC
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index e6a3154721faa..cf1641b923049 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -114,11 +114,10 @@ bool mlir::emitc::isIntegerIndexOrOpaqueType(Type type) {
 bool mlir::emitc::isSupportedFloatType(Type type) {
   if (auto floatType = llvm::dyn_cast<FloatType>(type)) {
     switch (floatType.getWidth()) {
-    case 16: {
-      if (llvm::isa<Float16Type, BFloat16Type>(type))
-        return true;
-      return false;
-    }
+    case 16:
+      if (!llvm::isa<Float16Type, BFloat16Type>(type))
+        return false;
+      LLVM_FALLTHROUGH;
     case 32:
     case 64:
       return true;
@@ -294,25 +293,46 @@ LogicalResult emitc::AssignOp::verify() {
 }
 
 //===----------------------------------------------------------------------===//
-// CastOp
+// CallOp
 //===----------------------------------------------------------------------===//
 
-bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
-  Type input = inputs.front(), output = outputs.front();
+LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
+  // Check that the callee attribute was specified.
+  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
+  if (!fnAttr)
+    return emitOpError("requires a 'callee' symbol reference attribute");
+  FuncOp fn = symbolTable.lookupNearestSymbolFrom<FuncOp>(*this, fnAttr);
+  if (!fn)
+    return emitOpError() << "'" << fnAttr.getValue()
+                         << "' does not reference a valid function";
 
-  if (auto arrayType = dyn_cast<emitc::ArrayType>(input)) {
-    if (auto pointerType = dyn_cast<emitc::PointerType>(output)) {
-      return (arrayType.getElementType() == pointerType.getPointee()) &&
-             arrayType.getShape().size() == 1 && arrayType.getShape()[0] >= 1;
+  // Verify that the operand and result types match the callee.
+  auto fnType = fn.getFunctionType();
+  if (fnType.getNumInputs() != getNumOperands())
+    return emitOpError("incorrect number of operands for callee");
+
+  for (unsigned i = 0, e = fnType.getNumInputs(); i != e; ++i)
+    if (getOperand(i).getType() != fnType.getInput(i))
+      return emitOpError("operand type mismatch: expected operand type ")
+             << fnType.getInput(i) << ", but provided "
+             << getOperand(i).getType() << " for operand number " << i;
+
+  if (fnType.getNumResults() != getNumResults())
+    return emitOpError("incorrect number of results for callee");
+
+  for (unsigned i = 0, e = fnType.getNumResults(); i != e; ++i)
+    if (getResult(i).getType() != fnType.getResult(i)) {
+      auto diag = emitOpError("result type mismatch at index ") << i;
+      diag.attachNote() << "      op result types: " << getResultTypes();
+      diag.attachNote() << "function result types: " << fnType.getResults();
+      return diag;
     }
-    return false;
-  }
 
-  return (
-      (emitc::isIntegerIndexOrOpaqueType(input) ||
-       emitc::isSupportedFloatType(input) || isa<emitc::PointerType>(input)) &&
-      (emitc::isIntegerIndexOrOpaqueType(output) ||
-       emitc::isSupportedFloatType(output) || isa<emitc::PointerType>(output)));
+  return success();
+}
+
+FunctionType CallOp::getCalleeType() {
+  return FunctionType::get(getContext(), getOperandTypes(), getResultTypes());
 }
 
 //===----------------------------------------------------------------------===//
@@ -357,6 +377,28 @@ LogicalResult emitc::CallOpaqueOp::verify() {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// CastOp
+//===----------------------------------------------------------------------===//
+
+bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
+  Type input = inputs.front(), output = outputs.front();
+
+  if (auto arrayType = dyn_cast<emitc::ArrayType>(input)) {
+    if (auto pointerType = dyn_cast<emitc::PointerType>(output)) {
+      return (arrayType.getElementType() == pointerType.getPointee()) &&
+             arrayType.getShape().size() == 1 && arrayType.getShape()[0] >= 1;
+    }
+    return false;
+  }
+
+  return (
+      (emitc::isIntegerIndexOrOpaqueType(input) ||
+       emitc::isSupportedFloatType(input) || isa<emitc::PointerType>(input)) &&
+      (emitc::isIntegerIndexOrOpaqueType(output) ||
+       emitc::isSupportedFloatType(output) || isa<emitc::PointerType>(output)));
+}
+
 //===----------------------------------------------------------------------===//
 // ConstantOp
 //===----------------------------------------------------------------------===//
@@ -374,6 +416,24 @@ LogicalResult emitc::ConstantOp::verify() {
 
 OpFoldResult emitc::ConstantOp::fold(FoldAdaptor adaptor) { return getValue(); }
 
+//===----------------------------------------------------------------------===//
+// DeclareFuncOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult
+DeclareFuncOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
+  // Check that the sym_name attribute was specified.
+  auto fnAttr = getSymNameAttr();
+  if (!fnAttr)
+    return emitOpError("requires a 'sym_name' symbol reference attribute");
+  FuncOp fn = symbolTable.lookupNearestSymbolFrom<FuncOp>(*this, fnAttr);
+  if (!fn)
+    return emitOpError() << "'" << fnAttr.getValue()
+                         << "' does not reference a valid function";
+
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // ExpressionOp
 //===----------------------------------------------------------------------===//
@@ -424,6 +484,74 @@ LogicalResult ExpressionOp::verify() {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// FieldOp
+//===----------------------------------------------------------------------===//
+
+static void printEmitCFieldOpTypeAndInitialValue(OpAsmPrinter &p, FieldOp op,
+                                                 TypeAttr type,
+                                                 Attribute initialValue) {
+  p << type;
+  if (initialValue) {
+    p << " = ";
+    p.printAttributeWithoutType(initialValue);
+  }
+}
+
+static Type getInitializerTypeForField(Type type) {
+  if (auto array = llvm::dyn_cast<ArrayType>(type))
+    return RankedTensorType::get(array.getShape(), array.getElementType());
+  return type;
+}
+
+static ParseResult
+parseEmitCFieldOpTypeAndInitialValue(OpAsmParser &parser, TypeAttr &typeAttr,
+                                     Attribute &initialValue) {
+  Type type;
+  if (parser.parseType(type))
+    return failure();
+
+  typeAttr = TypeAttr::get(type);
+
+  if (parser.parseOptionalEqual())
+    return success();
+
+  if (parser.parseAttribute(initialValue, getInitializerTypeForField(type)))
+    return failure();
+
+  if (!llvm::isa<ElementsAttr, IntegerAttr, FloatAttr, emitc::OpaqueAttr>(
+          initialValue))
+    return parser.emitError(parser.getNameLoc())
+           << "initial value should be a integer, float, elements or opaque "
+              "attribute";
+  return success();
+}
+
+LogicalResult FieldOp::verify() {
+  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");
+
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// FileOp
+//===----------------------------------------------------------------------===//
+
+void FileOp::build(OpBuilder &builder, OperationState &state, StringRef id) {
+  state.addRegion()->emplaceBlock();
+  state.attributes.push_back(
+      builder.getNamedAttr("id", builder.getStringAttr(id)));
+}
+
 //===----------------------------------------------------------------------===//
 // ForOp
 //===----------------------------------------------------------------------===//
@@ -518,67 +646,6 @@ LogicalResult ForOp::verifyRegions() {
   return success();
 }
 
-//===----------------------------------------------------------------------===//
-// CallOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
-  // Check that the callee attribute was specified.
-  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
-  if (!fnAttr)
-    return emitOpError("requires a 'callee' symbol reference attribute");
-  FuncOp fn = symbolTable.lookupNearestSymbolFrom<FuncOp>(*this, fnAttr);
-  if (!fn)
-    return emitOpError() << "'" << fnAttr.getValue()
-                         << "' does not reference a valid function";
-
-  // Verify that the operand and result types match the callee.
-  auto fnType = fn.getFunctionType();
-  if (fnType.getNumInputs() != getNumOperands())
-    return emitOpError("incorrect number of operands for callee");
-
-  for (unsigned i = 0, e = fnType.getNumInputs(); i != e; ++i)
-    if (getOperand(i).getType() != fnType.getInput(i))
-      return emitOpError("operand type mismatch: expected operand type ")
-             << fnType.getInput(i) << ", but provided "
-             << getOperand(i).getType() << " for operand number " << i;
-
-  if (fnType.getNumResults() != getNumResults())
-    return emitOpError("incorrect number of results for callee");
-
-  for (unsigned i = 0, e = fnType.getNumResults(); i != e; ++i)
-    if (getResult(i).getType() != fnType.getResult(i)) {
-      auto diag = emitOpError("result type mismatch at index ") << i;
-      diag.attachNote() << "      op result types: " << getResultTypes();
-      diag.attachNote() << "function result types: " << fnType.getResults();
-      return diag;
-    }
-
-  return success();
-}
-
-FunctionType CallOp::getCalleeType() {
-  return FunctionType::get(getContext(), getOperandTypes(), getResultTypes());
-}
-
-//===----------------------------------------------------------------------===//
-// DeclareFuncOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult
-DeclareFuncOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
-  // Check that the sym_name attribute was specified.
-  auto fnAttr = getSymNameAttr();
-  if (!fnAttr)
-    return emitOpError("requires a 'sym_name' symbol reference attribute");
-  FuncOp fn = symbolTable.lookupNearestSymbolFrom<FuncOp>(*this, fnAttr);
-  if (!fn)
-    return emitOpError() << "'" << fnAttr.getValue()
-                         << "' does not reference a valid function";
-
-  return success();
-}
-
 //===----------------------------------------------------------------------===//
 // FuncOp
 //===----------------------------------------------------------------------===//
@@ -634,35 +701,153 @@ LogicalResult FuncOp::verify() {
 }
 
 //===----------------------------------------------------------------------===//
-// ReturnOp
+// GetFieldOp
 //===----------------------------------------------------------------------===//
 
-LogicalResult ReturnOp::verify() {
-  auto function = cast<FuncOp>((*this)->getParentOp());
+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";
 
-  // The operand number and types must match the function signature.
-  if (getNumOperands() != function.getNumResults())
-    return emitOpError("has ")
-           << getNumOperands() << " operands, but enclosing function (@"
-           << function.getName() << ") returns " << function.getNumResults();
+  Type getFieldResultType = getResult().getType();
+  Type fieldType = fieldOp.getType();
+
+  if (fieldType != getFieldResultType)
+    return emitOpError("result type ")
+           << getFieldResultType << " does not match field '" << fieldNameAttr
+           << "' type " << fieldType;
 
-  if (function.getNumResults() == 1)
-    if (getOperand().getType() != function.getResultTypes()[0])
-      return emitError() << "type of the return operand ("
-                         << getOperand().getType()
-                         << ") doesn't match function result type ("
-                         << function.getResultTypes()[0] << ")"
-                         << " in function @" << function.getName();
   return success();
 }
 
 //===----------------------------------------------------------------------===//
-// IfOp
+// GetGlobalOp
 //===----------------------------------------------------------------------===//
 
-void IfOp::build(OpBuilder &builder, OperationState &result, Value cond,
-                 bool addThenBlock, bool addElseBlock) {
-  assert((!addElseBlock || addThenBlock) &&
+LogicalResult
+GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
+  // Verify that the type matches the type of the global variable.
+  auto global =
+      symbolTable.lookupNearestSymbolFrom<GlobalOp>(*this, getNameAttr());
+  if (!global)
+    return emitOpError("'")
+           << getName() << "' does not reference a valid emitc.global";
+
+  Type resultType = getResult().getType();
+  Type globalType = global.getType();
+
+  // global has array type
+  if (llvm::isa<ArrayType>(globalType)) {
+    if (globalType != resultType)
+      return emitOpError("on array type expects result type ")
+             << resultType << " to match type " << globalType
+             << " of the global @" << getName();
+    return success();
+  }
+
+  // global has non-array type
+  auto lvalueType = dyn_cast<LValueType>(resultType);
+  if (!lvalueType || lvalueType.getValueType() != globalType)
+    return emitOpError("on non-array type expects result inner type ")
+           << lvalueType.getValueType() << " to match type " << globalType
+           << " of the global @" << getName();
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// GlobalOp
+//===----------------------------------------------------------------------===//
+
+static void printEmitCGlobalOpTypeAndInitialValue(OpAsmPrinter &p, GlobalOp op,
+                                                  TypeAttr type,
+                                                  Attribute initialValue) {
+  p << type;
+  if (initialValue) {
+    p << " = ";
+    p.printAttributeWithoutType(initialValue);
+  }
+}
+
+static Type getInitializerTypeForGlobal(Type type) {
+  if (auto array = llvm::dyn_cast<ArrayType>(type))
+    return RankedTensorType::get(array.getShape(), array.getElementType());
+  return type;
+}
+
+static ParseResult
+parseEmitCGlobalOpTypeAndInitialValue(OpAsmParser &parser, TypeAttr &typeAttr,
+                                      Attribute &initialValue) {
+  Type type;
+  if (parser.parseType(type))
+    return failure();
+
+  typeAttr = TypeAttr::get(type);
+
+  if (parser.parseOptionalEqual())
+    return success();
+
+  if (parser.parseAttribute(initialValue, getInitializerTypeForGlobal(type)))
+    return failure();
+
+  if (!llvm::isa<ElementsAttr, IntegerAttr, FloatAttr, emitc::OpaqueAttr>(
+          initialValue))
+    return parser.emitError(parser.getNameLoc())
+           << "initial value should be a integer, float, elements or opaque "
+              "attribute";
+  return success();
+}
+
+LogicalResult GlobalOp::verify() {
+  if (!isSupportedEmitCType(getType())) {
+    return emitOpError("expected valid emitc type");
+  }
+  if (getInitialValue().has_value()) {
+    Attribute initValue = getInitialValue().value();
+    // Check that the type of the initial value is compatible with the type of
+    // the global variable.
+    if (auto elementsAttr = llvm::dyn_cast<ElementsAttr>(initValue)) {
+      auto arrayType = llvm::dyn_cast<ArrayType>(getType());
+      if (!arrayType)
+        return emitOpError("expected array type, but got ") << getType();
+
+      Type initType = elementsAttr.getType();
+      Type tensorType = getInitializerTypeForGlobal(getType());
+      if (initType != tensorType) {
+        return emitOpError("initial value expected to be of type ")
+               << getType() << ", but was of type " << initType;
+      }
+    } else if (auto intAttr = dyn_cast<IntegerAttr>(initValue)) {
+      if (intAttr.getType() != getType()) {
+        return emitOpError("initial value expected to be of type ")
+               << getType() << ", but was of type " << intAttr.getType();
+      }
+    } else if (auto floatAttr = dyn_cast<FloatAttr>(initValue)) {
+      if (floatAttr.getType() != getType()) {
+        return emitOpError("initial value expected to be of type ")
+               << getType() << ", but was of type " << floatAttr.getType();
+      }
+    } else if (!isa<emitc::OpaqueAttr>(initValue)) {
+      return emitOpError("initial value should be a integer, float, elements "
+                         "or opaque attribute, but got ")
+             << initValue;
+    }
+  }
+  if (getStaticSpecifier() && getExternSpecifier()) {
+    return emitOpError("cannot have both static and extern specifiers");
+  }
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// IfOp
+//===----------------------------------------------------------------------===//
+
+void IfOp::build(OpBuilder &builder, OperationState &result, Value cond,
+                 bool addThenBlock, bool addElseBlock) {
+  assert((!addElseBlock || addThenBlock) &&
          "must not create else block w/o then block");
   result.addOperands(cond);
 
@@ -861,6 +1046,30 @@ LogicalResult emitc::LiteralOp::verify() {
     return emitOpError() << "value must not be empty";
   return success();
 }
+
+//===----------------------------------------------------------------------===//
+// ReturnOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult ReturnOp::verify() {
+  auto function = cast<FuncOp>((*this)->getParentOp());
+
+  // The operand number and types must match the function signature.
+  if (getNumOperands() != function.getNumResults())
+    return emitOpError("has ")
+           << getNumOperands() << " operands, but enclosing function (@"
+           << function.getName() << ") returns " << function.getNumResults();
+
+  if (function.getNumResults() == 1)
+    if (getOperand().getType() != function.getResultTypes()[0])
+      return emitError() << "type of the return operand ("
+                         << getOperand().getType()
+                         << ") doesn't match function result type ("
+                         << function.getResultTypes()[0] << ")"
+                         << " in function @" << function.getName();
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // SubOp
 //===----------------------------------------------------------------------===//
@@ -885,31 +1094,6 @@ LogicalResult SubOp::verify() {
   return success();
 }
 
-//===----------------------------------------------------------------------===//
-// VariableOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult emitc::VariableOp::verify() {
-  return verifyInitializationAttribute(getOperation(), getValueAttr());
-}
-
-//===----------------------------------------------------------------------===//
-// YieldOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult emitc::YieldOp::verify() {
-  Value result = getResult();
-  Operation *containingOp = getOperation()->getParentOp();
-
-  if (result && containingOp->getNumResults() != 1)
-    return emitOpError() << "yields a value not returned by parent";
-
-  if (!result && containingOp->getNumResults() != 0)
-    return emitOpError() << "does not yield a value to be returned by parent";
-
-  return success();
-}
-
 //===----------------------------------------------------------------------===//
 // SubscriptOp
 //===----------------------------------------------------------------------===//
@@ -976,57 +1160,214 @@ LogicalResult emitc::SubscriptOp::verify() {
 }
 
 //===----------------------------------------------------------------------===//
-// VerbatimOp
+// SwitchOp
 //===----------------------------------------------------------------------===//
 
-LogicalResult emitc::VerbatimOp::verify() {
-  auto errorCallback = [&]() -> InFlightDiagnostic {
-    return this->emitOpError();
-  };
-  FailureOr<SmallVector<ReplacementItem>> fmt =
-      ::parseFormatString(getValue(), getFmtArgs(), errorCallback);
-  if (failed(fmt))
-    return failure();
-
-  size_t numPlaceholders = llvm::count_if(*fmt, [](ReplacementItem &item) {
-    return std::holds_alternative<Placeholder>(item);
-  });
-
-  if (numPlaceholders != getFmtArgs().size()) {
-    return emitOpError()
-           << "requires operands for each placeholder in the format string";
+/// Parse the case regions and values.
+static ParseResult
+parseSwitchCases(OpAsmParser &parser, DenseI64ArrayAttr &cases,
+                 SmallVectorImpl<std::unique_ptr<Region>> &caseRegions) {
+  SmallVector<int64_t> caseValues;
+  while (succeeded(parser.parseOptionalKeyword("case"))) {
+    int64_t value;
+    Region &region = *caseRegions.emplace_back(std::make_unique<Region>());
+    if (parser.parseInteger(value) ||
+        parser.parseRegion(region, /*arguments=*/{}))
+      return failure();
+    caseValues.push_back(value);
   }
+  cases = parser.getBuilder().getDenseI64ArrayAttr(caseValues);
   return success();
 }
 
-FailureOr<SmallVector<ReplacementItem>> emitc::VerbatimOp::parseFormatString() {
-  // Error checking is done in verify.
-  return ::parseFormatString(getValue(), getFmtArgs());
+/// Print the case regions and values.
+static void printSwitchCases(OpAsmPrinter &p, Operation *op,
+                             DenseI64ArrayAttr cases, RegionRange caseRegions) {
+  for (auto [value, region] : llvm::zip(cases.asArrayRef(), caseRegions)) {
+    p.printNewline();
+    p << "case " << value << ' ';
+    p.printRegion(*region, /*printEntryBlockArgs=*/false);
+  }
 }
 
-//===----------------------------------------------------------------------===//
-// EmitC Enums
-//===----------------------------------------------------------------------===//
+static LogicalResult verifyRegion(emitc::SwitchOp op, Region &region,
+                                  const Twine &name) {
+  auto yield = dyn_cast<emitc::YieldOp>(region.front().back());
+  if (!yield)
+    return op.emitOpError("expected region to end with emitc.yield, but got ")
+           << region.front().back().getName();
 
-#include "mlir/Dialect/EmitC/IR/EmitCEnums.cpp.inc"
+  if (yield.getNumOperands() != 0) {
+    return (op.emitOpError("expected each region to return ")
+            << "0 values, but " << name << " returns "
+            << yield.getNumOperands())
+               .attachNote(yield.getLoc())
+           << "see yield operation here";
+  }
 
-//===----------------------------------------------------------------------===//
-// EmitC Attributes
-//===----------------------------------------------------------------------===//
+  return success();
+}
 
-#define GET_ATTRDEF_CLASSES
-#include "mlir/Dialect/EmitC/IR/EmitCAttributes.cpp.inc"
+LogicalResult emitc::SwitchOp::verify() {
+  if (!isIntegerIndexOrOpaqueType(getArg().getType()))
+    return emitOpError("unsupported type ") << getArg().getType();
 
-//===----------------------------------------------------------------------===//
-// EmitC Types
-//===----------------------------------------------------------------------===//
+  if (getCases().size() != getCaseRegions().size()) {
+    return emitOpError("has ")
+           << getCaseRegions().size() << " case regions but "
+           << getCases().size() << " case values";
+  }
 
-#define GET_TYPEDEF_CLASSES
-#include "mlir/Dialect/EmitC/IR/EmitCTypes.cpp.inc"
+  DenseSet<int64_t> valueSet;
+  for (int64_t value : getCases())
+    if (!valueSet.insert(value).second)
+      return emitOpError("has duplicate case value: ") << value;
 
-//===----------------------------------------------------------------------===//
-// ArrayType
-//===----------------------------------------------------------------------===//
+  if (failed(verifyRegion(*this, getDefaultRegion(), "default region")))
+    return failure();
+
+  for (auto [idx, caseRegion] : llvm::enumerate(getCaseRegions()))
+    if (failed(verifyRegion(*this, caseRegion, "case region #" + Twine(idx))))
+      return failure();
+
+  return success();
+}
+
+unsigned emitc::SwitchOp::getNumCases() { return getCases().size(); }
+
+Block &emitc::SwitchOp::getDefaultBlock() { return getDefaultRegion().front(); }
+
+Block &emitc::SwitchOp::getCaseBlock(unsigned idx) {
+  assert(idx < getNumCases() && "case index out-of-bounds");
+  return getCaseRegions()[idx].front();
+}
+
+void SwitchOp::getSuccessorRegions(
+    RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &successors) {
+  llvm::append_range(successors, getRegions());
+}
+
+void SwitchOp::getEntrySuccessorRegions(
+    ArrayRef<Attribute> operands,
+    SmallVectorImpl<RegionSuccessor> &successors) {
+  FoldAdaptor adaptor(operands, *this);
+
+  // If a constant was not provided, all regions are possible successors.
+  auto arg = dyn_cast_or_null<IntegerAttr>(adaptor.getArg());
+  if (!arg) {
+    llvm::append_range(successors, getRegions());
+    return;
+  }
+
+  // Otherwise, try to find a case with a matching value. If not, the
+  // default region is the only successor.
+  for (auto [caseValue, caseRegion] : llvm::zip(getCases(), getCaseRegions())) {
+    if (caseValue == arg.getInt()) {
+      successors.emplace_back(&caseRegion);
+      return;
+    }
+  }
+  successors.emplace_back(&getDefaultRegion());
+}
+
+void SwitchOp::getRegionInvocationBounds(
+    ArrayRef<Attribute> operands, SmallVectorImpl<InvocationBounds> &bounds) {
+  auto operandValue = llvm::dyn_cast_or_null<IntegerAttr>(operands.front());
+  if (!operandValue) {
+    // All regions are invoked at most once.
+    bounds.append(getNumRegions(), InvocationBounds(/*lb=*/0, /*ub=*/1));
+    return;
+  }
+
+  unsigned liveIndex = getNumRegions() - 1;
+  const auto *iteratorToInt = llvm::find(getCases(), operandValue.getInt());
+
+  liveIndex = iteratorToInt != getCases().end()
+                  ? std::distance(getCases().begin(), iteratorToInt)
+                  : liveIndex;
+
+  for (unsigned regIndex = 0, regNum = getNumRegions(); regIndex < regNum;
+       ++regIndex)
+    bounds.emplace_back(/*lb=*/0, /*ub=*/regIndex == liveIndex);
+}
+
+//===----------------------------------------------------------------------===//
+// VariableOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult emitc::VariableOp::verify() {
+  return verifyInitializationAttribute(getOperation(), getValueAttr());
+}
+
+//===----------------------------------------------------------------------===//
+// VerbatimOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult emitc::VerbatimOp::verify() {
+  auto errorCallback = [&]() -> InFlightDiagnostic {
+    return this->emitOpError();
+  };
+  FailureOr<SmallVector<ReplacementItem>> fmt =
+      ::parseFormatString(getValue(), getFmtArgs(), errorCallback);
+  if (failed(fmt))
+    return failure();
+
+  size_t numPlaceholders = llvm::count_if(*fmt, [](ReplacementItem &item) {
+    return std::holds_alternative<Placeholder>(item);
+  });
+
+  if (numPlaceholders != getFmtArgs().size()) {
+    return emitOpError()
+           << "requires operands for each placeholder in the format string";
+  }
+  return success();
+}
+
+FailureOr<SmallVector<ReplacementItem>> emitc::VerbatimOp::parseFormatString() {
+  // Error checking is done in verify.
+  return ::parseFormatString(getValue(), getFmtArgs());
+}
+
+//===----------------------------------------------------------------------===//
+// YieldOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult emitc::YieldOp::verify() {
+  Value result = getResult();
+  Operation *containingOp = getOperation()->getParentOp();
+
+  if (result && containingOp->getNumResults() != 1)
+    return emitOpError() << "yields a value not returned by parent";
+
+  if (!result && containingOp->getNumResults() != 0)
+    return emitOpError() << "does not yield a value to be returned by parent";
+
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// EmitC Enums
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/EmitC/IR/EmitCEnums.cpp.inc"
+
+//===----------------------------------------------------------------------===//
+// EmitC Attributes
+//===----------------------------------------------------------------------===//
+
+#define GET_ATTRDEF_CLASSES
+#include "mlir/Dialect/EmitC/IR/EmitCAttributes.cpp.inc"
+
+//===----------------------------------------------------------------------===//
+// EmitC Types
+//===----------------------------------------------------------------------===//
+
+#define GET_TYPEDEF_CLASSES
+#include "mlir/Dialect/EmitC/IR/EmitCTypes.cpp.inc"
+
+//===----------------------------------------------------------------------===//
+// ArrayType
+//===----------------------------------------------------------------------===//
 
 Type emitc::ArrayType::parse(AsmParser &parser) {
   if (parser.parseLess())
@@ -1137,343 +1478,6 @@ LogicalResult mlir::emitc::PointerType::verify(
   return success();
 }
 
-//===----------------------------------------------------------------------===//
-// GlobalOp
-//===----------------------------------------------------------------------===//
-static void printEmitCGlobalOpTypeAndInitialValue(OpAsmPrinter &p, GlobalOp op,
-                                                  TypeAttr type,
-                                                  Attribute initialValue) {
-  p << type;
-  if (initialValue) {
-    p << " = ";
-    p.printAttributeWithoutType(initialValue);
-  }
-}
-
-static Type getInitializerTypeForGlobal(Type type) {
-  if (auto array = llvm::dyn_cast<ArrayType>(type))
-    return RankedTensorType::get(array.getShape(), array.getElementType());
-  return type;
-}
-
-static ParseResult
-parseEmitCGlobalOpTypeAndInitialValue(OpAsmParser &parser, TypeAttr &typeAttr,
-                                      Attribute &initialValue) {
-  Type type;
-  if (parser.parseType(type))
-    return failure();
-
-  typeAttr = TypeAttr::get(type);
-
-  if (parser.parseOptionalEqual())
-    return success();
-
-  if (parser.parseAttribute(initialValue, getInitializerTypeForGlobal(type)))
-    return failure();
-
-  if (!llvm::isa<ElementsAttr, IntegerAttr, FloatAttr, emitc::OpaqueAttr>(
-          initialValue))
-    return parser.emitError(parser.getNameLoc())
-           << "initial value should be a integer, float, elements or opaque "
-              "attribute";
-  return success();
-}
-
-LogicalResult GlobalOp::verify() {
-  if (!isSupportedEmitCType(getType())) {
-    return emitOpError("expected valid emitc type");
-  }
-  if (getInitialValue().has_value()) {
-    Attribute initValue = getInitialValue().value();
-    // Check that the type of the initial value is compatible with the type of
-    // the global variable.
-    if (auto elementsAttr = llvm::dyn_cast<ElementsAttr>(initValue)) {
-      auto arrayType = llvm::dyn_cast<ArrayType>(getType());
-      if (!arrayType)
-        return emitOpError("expected array type, but got ") << getType();
-
-      Type initType = elementsAttr.getType();
-      Type tensorType = getInitializerTypeForGlobal(getType());
-      if (initType != tensorType) {
-        return emitOpError("initial value expected to be of type ")
-               << getType() << ", but was of type " << initType;
-      }
-    } else if (auto intAttr = dyn_cast<IntegerAttr>(initValue)) {
-      if (intAttr.getType() != getType()) {
-        return emitOpError("initial value expected to be of type ")
-               << getType() << ", but was of type " << intAttr.getType();
-      }
-    } else if (auto floatAttr = dyn_cast<FloatAttr>(initValue)) {
-      if (floatAttr.getType() != getType()) {
-        return emitOpError("initial value expected to be of type ")
-               << getType() << ", but was of type " << floatAttr.getType();
-      }
-    } else if (!isa<emitc::OpaqueAttr>(initValue)) {
-      return emitOpError("initial value should be a integer, float, elements "
-                         "or opaque attribute, but got ")
-             << initValue;
-    }
-  }
-  if (getStaticSpecifier() && getExternSpecifier()) {
-    return emitOpError("cannot have both static and extern specifiers");
-  }
-  return success();
-}
-
-//===----------------------------------------------------------------------===//
-// GetGlobalOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult
-GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
-  // Verify that the type matches the type of the global variable.
-  auto global =
-      symbolTable.lookupNearestSymbolFrom<GlobalOp>(*this, getNameAttr());
-  if (!global)
-    return emitOpError("'")
-           << getName() << "' does not reference a valid emitc.global";
-
-  Type resultType = getResult().getType();
-  Type globalType = global.getType();
-
-  // global has array type
-  if (llvm::isa<ArrayType>(globalType)) {
-    if (globalType != resultType)
-      return emitOpError("on array type expects result type ")
-             << resultType << " to match type " << globalType
-             << " of the global @" << getName();
-    return success();
-  }
-
-  // global has non-array type
-  auto lvalueType = dyn_cast<LValueType>(resultType);
-  if (!lvalueType || lvalueType.getValueType() != globalType)
-    return emitOpError("on non-array type expects result inner type ")
-           << lvalueType.getValueType() << " to match type " << globalType
-           << " of the global @" << getName();
-  return success();
-}
-
-//===----------------------------------------------------------------------===//
-// SwitchOp
-//===----------------------------------------------------------------------===//
-
-/// Parse the case regions and values.
-static ParseResult
-parseSwitchCases(OpAsmParser &parser, DenseI64ArrayAttr &cases,
-                 SmallVectorImpl<std::unique_ptr<Region>> &caseRegions) {
-  SmallVector<int64_t> caseValues;
-  while (succeeded(parser.parseOptionalKeyword("case"))) {
-    int64_t value;
-    Region &region = *caseRegions.emplace_back(std::make_unique<Region>());
-    if (parser.parseInteger(value) ||
-        parser.parseRegion(region, /*arguments=*/{}))
-      return failure();
-    caseValues.push_back(value);
-  }
-  cases = parser.getBuilder().getDenseI64ArrayAttr(caseValues);
-  return success();
-}
-
-/// Print the case regions and values.
-static void printSwitchCases(OpAsmPrinter &p, Operation *op,
-                             DenseI64ArrayAttr cases, RegionRange caseRegions) {
-  for (auto [value, region] : llvm::zip(cases.asArrayRef(), caseRegions)) {
-    p.printNewline();
-    p << "case " << value << ' ';
-    p.printRegion(*region, /*printEntryBlockArgs=*/false);
-  }
-}
-
-static LogicalResult verifyRegion(emitc::SwitchOp op, Region &region,
-                                  const Twine &name) {
-  auto yield = dyn_cast<emitc::YieldOp>(region.front().back());
-  if (!yield)
-    return op.emitOpError("expected region to end with emitc.yield, but got ")
-           << region.front().back().getName();
-
-  if (yield.getNumOperands() != 0) {
-    return (op.emitOpError("expected each region to return ")
-            << "0 values, but " << name << " returns "
-            << yield.getNumOperands())
-               .attachNote(yield.getLoc())
-           << "see yield operation here";
-  }
-
-  return success();
-}
-
-LogicalResult emitc::SwitchOp::verify() {
-  if (!isIntegerIndexOrOpaqueType(getArg().getType()))
-    return emitOpError("unsupported type ") << getArg().getType();
-
-  if (getCases().size() != getCaseRegions().size()) {
-    return emitOpError("has ")
-           << getCaseRegions().size() << " case regions but "
-           << getCases().size() << " case values";
-  }
-
-  DenseSet<int64_t> valueSet;
-  for (int64_t value : getCases())
-    if (!valueSet.insert(value).second)
-      return emitOpError("has duplicate case value: ") << value;
-
-  if (failed(verifyRegion(*this, getDefaultRegion(), "default region")))
-    return failure();
-
-  for (auto [idx, caseRegion] : llvm::enumerate(getCaseRegions()))
-    if (failed(verifyRegion(*this, caseRegion, "case region #" + Twine(idx))))
-      return failure();
-
-  return success();
-}
-
-unsigned emitc::SwitchOp::getNumCases() { return getCases().size(); }
-
-Block &emitc::SwitchOp::getDefaultBlock() { return getDefaultRegion().front(); }
-
-Block &emitc::SwitchOp::getCaseBlock(unsigned idx) {
-  assert(idx < getNumCases() && "case index out-of-bounds");
-  return getCaseRegions()[idx].front();
-}
-
-void SwitchOp::getSuccessorRegions(
-    RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &successors) {
-  llvm::append_range(successors, getRegions());
-}
-
-void SwitchOp::getEntrySuccessorRegions(
-    ArrayRef<Attribute> operands,
-    SmallVectorImpl<RegionSuccessor> &successors) {
-  FoldAdaptor adaptor(operands, *this);
-
-  // If a constant was not provided, all regions are possible successors.
-  auto arg = dyn_cast_or_null<IntegerAttr>(adaptor.getArg());
-  if (!arg) {
-    llvm::append_range(successors, getRegions());
-    return;
-  }
-
-  // Otherwise, try to find a case with a matching value. If not, the
-  // default region is the only successor.
-  for (auto [caseValue, caseRegion] : llvm::zip(getCases(), getCaseRegions())) {
-    if (caseValue == arg.getInt()) {
-      successors.emplace_back(&caseRegion);
-      return;
-    }
-  }
-  successors.emplace_back(&getDefaultRegion());
-}
-
-void SwitchOp::getRegionInvocationBounds(
-    ArrayRef<Attribute> operands, SmallVectorImpl<InvocationBounds> &bounds) {
-  auto operandValue = llvm::dyn_cast_or_null<IntegerAttr>(operands.front());
-  if (!operandValue) {
-    // All regions are invoked at most once.
-    bounds.append(getNumRegions(), InvocationBounds(/*lb=*/0, /*ub=*/1));
-    return;
-  }
-
-  unsigned liveIndex = getNumRegions() - 1;
-  const auto *iteratorToInt = llvm::find(getCases(), operandValue.getInt());
-
-  liveIndex = iteratorToInt != getCases().end()
-                  ? std::distance(getCases().begin(), iteratorToInt)
-                  : liveIndex;
-
-  for (unsigned regIndex = 0, regNum = getNumRegions(); regIndex < regNum;
-       ++regIndex)
-    bounds.emplace_back(/*lb=*/0, /*ub=*/regIndex == liveIndex);
-}
-
-//===----------------------------------------------------------------------===//
-// FileOp
-//===----------------------------------------------------------------------===//
-void FileOp::build(OpBuilder &builder, OperationState &state, StringRef id) {
-  state.addRegion()->emplaceBlock();
-  state.attributes.push_back(
-      builder.getNamedAttr("id", builder.getStringAttr(id)));
-}
-
-//===----------------------------------------------------------------------===//
-// FieldOp
-//===----------------------------------------------------------------------===//
-static void printEmitCFieldOpTypeAndInitialValue(OpAsmPrinter &p, FieldOp op,
-                                                 TypeAttr type,
-                                                 Attribute initialValue) {
-  p << type;
-  if (initialValue) {
-    p << " = ";
-    p.printAttributeWithoutType(initialValue);
-  }
-}
-
-static Type getInitializerTypeForField(Type type) {
-  if (auto array = llvm::dyn_cast<ArrayType>(type))
-    return RankedTensorType::get(array.getShape(), array.getElementType());
-  return type;
-}
-
-static ParseResult
-parseEmitCFieldOpTypeAndInitialValue(OpAsmParser &parser, TypeAttr &typeAttr,
-                                     Attribute &initialValue) {
-  Type type;
-  if (parser.parseType(type))
-    return failure();
-
-  typeAttr = TypeAttr::get(type);
-
-  if (parser.parseOptionalEqual())
-    return success();
-
-  if (parser.parseAttribute(initialValue, getInitializerTypeForField(type)))
-    return failure();
-
-  if (!llvm::isa<ElementsAttr, IntegerAttr, FloatAttr, emitc::OpaqueAttr>(
-          initialValue))
-    return parser.emitError(parser.getNameLoc())
-           << "initial value should be a integer, float, elements or opaque "
-              "attribute";
-  return success();
-}
-
-LogicalResult FieldOp::verify() {
-  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");
-
-  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
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/EmitC/attrs.mlir b/mlir/test/Dialect/EmitC/attrs.mlir
index 11251b88ff0c9..5a219c462678e 100644
--- a/mlir/test/Dialect/EmitC/attrs.mlir
+++ b/mlir/test/Dialect/EmitC/attrs.mlir
@@ -1,6 +1,6 @@
-// RUN: mlir-opt -verify-diagnostics %s | FileCheck %s
+// RUN: mlir-opt %s | FileCheck %s
 // check parser
-// RUN: mlir-opt -verify-diagnostics %s | mlir-opt -verify-diagnostics | FileCheck %s
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s
 
 // CHECK-LABEL: func @opaque_attrs() {
 func.func @opaque_attrs() {
diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir
index ad40313f95df9..acee0a8d53fe4 100644
--- a/mlir/test/Dialect/EmitC/ops.mlir
+++ b/mlir/test/Dialect/EmitC/ops.mlir
@@ -310,3 +310,15 @@ func.func @switch() {
 
   return 
 }
+
+emitc.class final @finalClass {
+  emitc.field @fieldName0 : !emitc.array<1xf32>
+  emitc.field @fieldName1 : !emitc.array<1xf32>
+  emitc.func @execute() {
+    %0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
+    %1 = get_field @fieldName0 : !emitc.array<1xf32>
+    %2 = get_field @fieldName1 : !emitc.array<1xf32>
+    %3 = subscript %1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
+    return
+  }
+}
diff --git a/mlir/test/Dialect/EmitC/transforms.mlir b/mlir/test/Dialect/EmitC/transforms.mlir
index a38f396dad953..d9f4ee0c3fc41 100644
--- a/mlir/test/Dialect/EmitC/transforms.mlir
+++ b/mlir/test/Dialect/EmitC/transforms.mlir
@@ -1,16 +1,17 @@
-// RUN: mlir-opt %s --form-expressions --verify-diagnostics --split-input-file | FileCheck %s
-
-// CHECK-LABEL: func.func @single_expression(
-// CHECK-SAME:                               %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32, %[[VAL_3:.*]]: i32) -> i1 {
-// CHECK:           %[[VAL_4:.*]] = "emitc.constant"() <{value = 42 : i32}> : () -> i32
-// CHECK:           %[[VAL_5:.*]] = emitc.expression : i1 {
-// CHECK:             %[[VAL_6:.*]] = mul %[[VAL_0]], %[[VAL_4]] : (i32, i32) -> i32
-// CHECK:             %[[VAL_7:.*]] = sub %[[VAL_6]], %[[VAL_2]] : (i32, i32) -> i32
-// CHECK:             %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_3]] : (i32, i32) -> i1
-// CHECK:             yield %[[VAL_8]] : i1
-// CHECK:           }
-// CHECK:           return %[[VAL_5]] : i1
-// CHECK:       }
+// RUN: mlir-opt %s -form-expressions | FileCheck %s -check-prefix=CHECK-FORM-EXPRESSIONS
+// RUN: mlir-opt %s -wrap-emitc-func-in-class -split-input-file | FileCheck %s -check-prefix=CHECK-WRAP-IN-CLASS
+
+// CHECK-FORM-EXPRESSIONS-LABEL: func.func @single_expression(
+// CHECK-FORM-EXPRESSIONS-SAME:                               %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32, %[[VAL_3:.*]]: i32) -> i1 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_4:.*]] = "emitc.constant"() <{value = 42 : i32}> : () -> i32
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_5:.*]] = emitc.expression : i1 {
+// CHECK-FORM-EXPRESSIONS:             %[[VAL_6:.*]] = mul %[[VAL_0]], %[[VAL_4]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:             %[[VAL_7:.*]] = sub %[[VAL_6]], %[[VAL_2]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:             %[[VAL_8:.*]] = cmp lt, %[[VAL_7]], %[[VAL_3]] : (i32, i32) -> i1
+// CHECK-FORM-EXPRESSIONS:             yield %[[VAL_8]] : i1
+// CHECK-FORM-EXPRESSIONS:           }
+// CHECK-FORM-EXPRESSIONS:           return %[[VAL_5]] : i1
+// CHECK-FORM-EXPRESSIONS:       }
 
 func.func @single_expression(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i1 {
   %c42 = "emitc.constant"(){value = 42 : i32} : () -> i32
@@ -20,20 +21,20 @@ func.func @single_expression(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) ->
   return %c : i1
 }
 
-// CHECK-LABEL: func.func @multiple_expressions(
-// CHECK-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32, %[[VAL_3:.*]]: i32) -> (i32, i32) {
-// CHECK:         %[[VAL_4:.*]] = emitc.expression : i32 {
-// CHECK:           %[[VAL_5:.*]] = mul %[[VAL_0]], %[[VAL_1]] : (i32, i32) -> i32
-// CHECK:           %[[VAL_6:.*]] = sub %[[VAL_5]], %[[VAL_2]] : (i32, i32) -> i32
-// CHECK:           yield %[[VAL_6]] : i32
-// CHECK:         }
-// CHECK:         %[[VAL_7:.*]] = emitc.expression : i32 {
-// CHECK:           %[[VAL_8:.*]] = add %[[VAL_1]], %[[VAL_3]] : (i32, i32) -> i32
-// CHECK:           %[[VAL_9:.*]] = div %[[VAL_8]], %[[VAL_2]] : (i32, i32) -> i32
-// CHECK:           yield %[[VAL_9]] : i32
-// CHECK:         }
-// CHECK:         return %[[VAL_4]], %[[VAL_7]] : i32, i32
-// CHECK:       }
+// CHECK-FORM-EXPRESSIONS-LABEL: func.func @multiple_expressions(
+// CHECK-FORM-EXPRESSIONS-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32, %[[VAL_3:.*]]: i32) -> (i32, i32) {
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_4:.*]] = emitc.expression : i32 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_5:.*]] = mul %[[VAL_0]], %[[VAL_1]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_6:.*]] = sub %[[VAL_5]], %[[VAL_2]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:           yield %[[VAL_6]] : i32
+// CHECK-FORM-EXPRESSIONS:         }
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_7:.*]] = emitc.expression : i32 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_8:.*]] = add %[[VAL_1]], %[[VAL_3]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_9:.*]] = div %[[VAL_8]], %[[VAL_2]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:           yield %[[VAL_9]] : i32
+// CHECK-FORM-EXPRESSIONS:         }
+// CHECK-FORM-EXPRESSIONS:         return %[[VAL_4]], %[[VAL_7]] : i32, i32
+// CHECK-FORM-EXPRESSIONS:       }
 
 func.func @multiple_expressions(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> (i32, i32) {
   %a = emitc.mul %arg0, %arg1 : (i32, i32) -> i32
@@ -43,19 +44,19 @@ func.func @multiple_expressions(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32)
   return %b, %d : i32, i32
 }
 
-// CHECK-LABEL: func.func @expression_with_call(
-// CHECK-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32, %[[VAL_3:.*]]: i32) -> i1 {
-// CHECK:         %[[VAL_4:.*]] = emitc.expression : i32 {
-// CHECK:           %[[VAL_5:.*]] = mul %[[VAL_0]], %[[VAL_1]] : (i32, i32) -> i32
-// CHECK:           %[[VAL_6:.*]] = call_opaque "foo"(%[[VAL_5]], %[[VAL_2]]) : (i32, i32) -> i32
-// CHECK:           yield %[[VAL_6]] : i32
-// CHECK:         }
-// CHECK:         %[[VAL_7:.*]] = emitc.expression : i1 {
-// CHECK:           %[[VAL_8:.*]] = cmp lt, %[[VAL_4]], %[[VAL_1]] : (i32, i32) -> i1
-// CHECK:           yield %[[VAL_8]] : i1
-// CHECK:         }
-// CHECK:         return %[[VAL_7]] : i1
-// CHECK:       }
+// CHECK-FORM-EXPRESSIONS-LABEL: func.func @expression_with_call(
+// CHECK-FORM-EXPRESSIONS-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i32, %[[VAL_3:.*]]: i32) -> i1 {
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_4:.*]] = emitc.expression : i32 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_5:.*]] = mul %[[VAL_0]], %[[VAL_1]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_6:.*]] = call_opaque "foo"(%[[VAL_5]], %[[VAL_2]]) : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:           yield %[[VAL_6]] : i32
+// CHECK-FORM-EXPRESSIONS:         }
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_7:.*]] = emitc.expression : i1 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_8:.*]] = cmp lt, %[[VAL_4]], %[[VAL_1]] : (i32, i32) -> i1
+// CHECK-FORM-EXPRESSIONS:           yield %[[VAL_8]] : i1
+// CHECK-FORM-EXPRESSIONS:         }
+// CHECK-FORM-EXPRESSIONS:         return %[[VAL_7]] : i1
+// CHECK-FORM-EXPRESSIONS:       }
 
 func.func @expression_with_call(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i1 {
   %a = emitc.mul %arg0, %arg1 : (i32, i32) -> i32
@@ -64,17 +65,17 @@ func.func @expression_with_call(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32)
   return %c : i1
 }
 
-// CHECK-LABEL: func.func @expression_with_dereference(
-// CHECK-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: !emitc.ptr<i32>) -> i1 {
-// CHECK:         %[[VAL_3:.*]] = emitc.expression : i32 {
-// CHECK:           %[[VAL_4:.*]] = apply "*"(%[[VAL_2]]) : (!emitc.ptr<i32>) -> i32
-// CHECK:           yield %[[VAL_4]] : i32
-// CHECK:         }
-// CHECK:         %[[VAL_5:.*]] = emitc.expression : i1 {
-// CHECK:           %[[VAL_6:.*]] = mul %[[VAL_0]], %[[VAL_1]] : (i32, i32) -> i32
-// CHECK:           %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_3]] : (i32, i32) -> i1
-// CHECK:         return %[[VAL_5]] : i1
-// CHECK:       }
+// CHECK-FORM-EXPRESSIONS-LABEL: func.func @expression_with_dereference(
+// CHECK-FORM-EXPRESSIONS-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: !emitc.ptr<i32>) -> i1 {
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_3:.*]] = emitc.expression : i32 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_4:.*]] = apply "*"(%[[VAL_2]]) : (!emitc.ptr<i32>) -> i32
+// CHECK-FORM-EXPRESSIONS:           yield %[[VAL_4]] : i32
+// CHECK-FORM-EXPRESSIONS:         }
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_5:.*]] = emitc.expression : i1 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_6:.*]] = mul %[[VAL_0]], %[[VAL_1]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_3]] : (i32, i32) -> i1
+// CHECK-FORM-EXPRESSIONS:         return %[[VAL_5]] : i1
+// CHECK-FORM-EXPRESSIONS:       }
 
 func.func @expression_with_dereference(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr<i32>) -> i1 {
   %a = emitc.mul %arg0, %arg1 : (i32, i32) -> i32
@@ -84,17 +85,17 @@ func.func @expression_with_dereference(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr
 }
 
 
-// CHECK-LABEL: func.func @expression_with_address_taken(
-// CHECK-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: !emitc.ptr<i32>) -> i1 {
-// CHECK:         %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
-// CHECK:         %[[VAL_4:.*]] = emitc.expression : i1 {
-// CHECK:           %[[VAL_5:.*]] = apply "&"(%[[VAL_3]]) : (!emitc.lvalue<i32>) -> !emitc.ptr<i32>
-// CHECK:           %[[VAL_6:.*]] = add %[[VAL_5]], %[[VAL_1]] : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32>
-// CHECK:           %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_2]] : (!emitc.ptr<i32>, !emitc.ptr<i32>) -> i1
-// CHECK:           yield %[[VAL_7]] : i1
-// CHECK:         }
-// CHECK:         return %[[VAL_4]] : i1
-// CHECK:       }
+// CHECK-FORM-EXPRESSIONS-LABEL: func.func @expression_with_address_taken(
+// CHECK-FORM-EXPRESSIONS-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: !emitc.ptr<i32>) -> i1 {
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_4:.*]] = emitc.expression : i1 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_5:.*]] = apply "&"(%[[VAL_3]]) : (!emitc.lvalue<i32>) -> !emitc.ptr<i32>
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_6:.*]] = add %[[VAL_5]], %[[VAL_1]] : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32>
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_7:.*]] = cmp lt, %[[VAL_6]], %[[VAL_2]] : (!emitc.ptr<i32>, !emitc.ptr<i32>) -> i1
+// CHECK-FORM-EXPRESSIONS:           yield %[[VAL_7]] : i1
+// CHECK-FORM-EXPRESSIONS:         }
+// CHECK-FORM-EXPRESSIONS:         return %[[VAL_4]] : i1
+// CHECK-FORM-EXPRESSIONS:       }
 
 func.func @expression_with_address_taken(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr<i32>) -> i1 {
   %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
@@ -104,14 +105,14 @@ func.func @expression_with_address_taken(%arg0: i32, %arg1: i32, %arg2: !emitc.p
   return %c : i1
 }
 
-// CHECK-LABEL: func.func @no_nested_expression(
-// CHECK-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32) -> i1 {
-// CHECK:         %[[VAL_2:.*]] = emitc.expression : i1 {
-// CHECK:           %[[VAL_3:.*]] = cmp lt, %[[VAL_0]], %[[VAL_1]] : (i32, i32) -> i1
-// CHECK:           yield %[[VAL_3]] : i1
-// CHECK:         }
-// CHECK:         return %[[VAL_2]] : i1
-// CHECK:       }
+// CHECK-FORM-EXPRESSIONS-LABEL: func.func @no_nested_expression(
+// CHECK-FORM-EXPRESSIONS-SAME:      %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32) -> i1 {
+// CHECK-FORM-EXPRESSIONS:         %[[VAL_2:.*]] = emitc.expression : i1 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_3:.*]] = cmp lt, %[[VAL_0]], %[[VAL_1]] : (i32, i32) -> i1
+// CHECK-FORM-EXPRESSIONS:           yield %[[VAL_3]] : i1
+// CHECK-FORM-EXPRESSIONS:         }
+// CHECK-FORM-EXPRESSIONS:         return %[[VAL_2]] : i1
+// CHECK-FORM-EXPRESSIONS:       }
 
 func.func @no_nested_expression(%arg0: i32, %arg1: i32) -> i1 {
   %a = emitc.expression : i1 {
@@ -121,37 +122,35 @@ func.func @no_nested_expression(%arg0: i32, %arg1: i32) -> i1 {
   return %a : i1
 }
 
-
-// CHECK-LABEL: func.func @single_result_requirement
-//   CHECK-NOT:  emitc.expression
+// CHECK-FORM-EXPRESSIONS-LABEL: func.func @single_result_requirement
+// CHECK-FORM-EXPRESSIONS-NOT:  emitc.expression
 
 func.func @single_result_requirement() -> (i32, i32) {
   %0:2 = emitc.call_opaque "foo" () : () -> (i32, i32)
   return %0#0, %0#1 : i32, i32
 }
 
-// CHECK-LABEL:   func.func @expression_with_load(
-// CHECK-SAME:                                    %[[VAL_0:.*]]: i32,
-// CHECK-SAME:                                    %[[VAL_1:.*]]: !emitc.ptr<i32>) -> i1 {
-// CHECK:           %[[VAL_2:.*]] = "emitc.constant"() <{value = 0 : i64}> : () -> i64
-// CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_4:.*]] = emitc.expression : i32 {
-// CHECK:             %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
-// CHECK:             yield %[[VAL_5]] : i32
-// CHECK:           }
-// CHECK:           %[[VAL_6:.*]] = emitc.subscript %[[VAL_1]]{{\[}}%[[VAL_2]]] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_7:.*]] = emitc.expression : i32 {
-// CHECK:             %[[VAL_8:.*]] = load %[[VAL_6]] : <i32>
-// CHECK:             yield %[[VAL_8]] : i32
-// CHECK:           }
-// CHECK:           %[[VAL_9:.*]] = emitc.expression : i1 {
-// CHECK:             %[[VAL_10:.*]] = add %[[VAL_4]], %[[VAL_7]] : (i32, i32) -> i32
-// CHECK:             %[[VAL_11:.*]] = cmp lt, %[[VAL_10]], %[[VAL_0]] : (i32, i32) -> i1
-// CHECK:             yield %[[VAL_11]] : i1
-// CHECK:           }
-// CHECK:           return %[[VAL_9]] : i1
-// CHECK:         }
-
+// CHECK-FORM-EXPRESSIONS-LABEL:   func.func @expression_with_load(
+// CHECK-FORM-EXPRESSIONS-SAME:                                    %[[VAL_0:.*]]: i32,
+// CHECK-FORM-EXPRESSIONS-SAME:                                    %[[VAL_1:.*]]: !emitc.ptr<i32>) -> i1 {
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_2:.*]] = "emitc.constant"() <{value = 0 : i64}> : () -> i64
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_4:.*]] = emitc.expression : i32 {
+// CHECK-FORM-EXPRESSIONS:             %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
+// CHECK-FORM-EXPRESSIONS:             yield %[[VAL_5]] : i32
+// CHECK-FORM-EXPRESSIONS:           }
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_6:.*]] = emitc.subscript %[[VAL_1]]{{\[}}%[[VAL_2]]] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_7:.*]] = emitc.expression : i32 {
+// CHECK-FORM-EXPRESSIONS:             %[[VAL_8:.*]] = load %[[VAL_6]] : <i32>
+// CHECK-FORM-EXPRESSIONS:             yield %[[VAL_8]] : i32
+// CHECK-FORM-EXPRESSIONS:           }
+// CHECK-FORM-EXPRESSIONS:           %[[VAL_9:.*]] = emitc.expression : i1 {
+// CHECK-FORM-EXPRESSIONS:             %[[VAL_10:.*]] = add %[[VAL_4]], %[[VAL_7]] : (i32, i32) -> i32
+// CHECK-FORM-EXPRESSIONS:             %[[VAL_11:.*]] = cmp lt, %[[VAL_10]], %[[VAL_0]] : (i32, i32) -> i1
+// CHECK-FORM-EXPRESSIONS:             yield %[[VAL_11]] : i1
+// CHECK-FORM-EXPRESSIONS:           }
+// CHECK-FORM-EXPRESSIONS:           return %[[VAL_9]] : i1
+// CHECK-FORM-EXPRESSIONS:         }
 
 func.func @expression_with_load(%arg0: i32, %arg1: !emitc.ptr<i32>) -> i1 {
   %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
@@ -163,3 +162,61 @@ func.func @expression_with_load(%arg0: i32, %arg1: !emitc.ptr<i32>) -> i1 {
   %c = emitc.cmp lt, %b, %arg0 :(i32, i32) -> i1
   return %c : i1
 }
+
+// -----
+
+emitc.func @foo(%arg0 : !emitc.array<1xf32>) {
+  emitc.call_opaque "bar" (%arg0) : (!emitc.array<1xf32>) -> ()
+  emitc.return
+}
+
+// CHECK-WRAP-IN-CLASS: module {
+// CHECK-WRAP-IN-CLASS:   emitc.class @fooClass {
+// CHECK-WRAP-IN-CLASS:     emitc.field @fieldName0 : !emitc.array<1xf32>
+// CHECK-WRAP-IN-CLASS:     emitc.func @execute() {
+// CHECK-WRAP-IN-CLASS:       %0 = get_field @fieldName0 : !emitc.array<1xf32>
+// CHECK-WRAP-IN-CLASS:       call_opaque "bar"(%0) : (!emitc.array<1xf32>) -> ()
+// CHECK-WRAP-IN-CLASS:       return
+// CHECK-WRAP-IN-CLASS:     }
+// CHECK-WRAP-IN-CLASS:   }
+// CHECK-WRAP-IN-CLASS: }
+
+// -----
+
+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-WRAP-IN-CLASS: module {
+// CHECK-WRAP-IN-CLASS:   emitc.class @modelClass {
+// CHECK-WRAP-IN-CLASS:     emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.name_hint = "another_feature"}
+// CHECK-WRAP-IN-CLASS:     emitc.field @fieldName1 : !emitc.array<1xf32>  {emitc.name_hint = "some_feature"}
+// CHECK-WRAP-IN-CLASS:     emitc.field @fieldName2 : !emitc.array<1xf32>  {emitc.name_hint = "output_0"}
+// CHECK-WRAP-IN-CLASS:     emitc.func @execute() {
+// CHECK-WRAP-IN-CLASS:       get_field @fieldName0 : !emitc.array<1xf32>
+// CHECK-WRAP-IN-CLASS:       get_field @fieldName1 : !emitc.array<1xf32>
+// CHECK-WRAP-IN-CLASS:       get_field @fieldName2 : !emitc.array<1xf32>
+// CHECK-WRAP-IN-CLASS:       "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
+// CHECK-WRAP-IN-CLASS:       subscript {{.*}}[{{.*}}] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
+// CHECK-WRAP-IN-CLASS:       load {{.*}} : <f32>
+// CHECK-WRAP-IN-CLASS:       subscript {{.*}}[{{.*}}] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
+// CHECK-WRAP-IN-CLASS:       load {{.*}} : <f32>
+// CHECK-WRAP-IN-CLASS:       add {{.*}}, {{.*}} : (f32, f32) -> f32
+// CHECK-WRAP-IN-CLASS:       subscript {{.*}}[{{.*}}] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
+// CHECK-WRAP-IN-CLASS:       assign {{.*}} : f32 to {{.*}} : <f32>
+// CHECK-WRAP-IN-CLASS:       return
+// CHECK-WRAP-IN-CLASS:     }
+// CHECK-WRAP-IN-CLASS:   }
+// CHECK-WRAP-IN-CLASS: }
diff --git a/mlir/test/Dialect/EmitC/types.mlir b/mlir/test/Dialect/EmitC/types.mlir
index d4dd94457f39b..ce1e03a83e5d1 100644
--- a/mlir/test/Dialect/EmitC/types.mlir
+++ b/mlir/test/Dialect/EmitC/types.mlir
@@ -1,6 +1,6 @@
-// RUN: mlir-opt -verify-diagnostics -allow-unregistered-dialect %s | FileCheck %s
-// check parser
-// RUN: mlir-opt -verify-diagnostics -allow-unregistered-dialect %s | mlir-opt -verify-diagnostics --allow-unregistered-dialect | FileCheck %s
+// RUN: mlir-opt %s -allow-unregistered-dialect | FileCheck %s
+// Check parser
+// RUN: mlir-opt %s -allow-unregistered-dialect | mlir-opt -allow-unregistered-dialect | FileCheck %s
 
 // CHECK-LABEL: func @array_types(
 func.func @array_types(
diff --git a/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir b/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir
deleted file mode 100644
index 029fa78a3f528..0000000000000
--- a/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir
+++ /dev/null
@@ -1,40 +0,0 @@
-// RUN: mlir-opt --wrap-emitc-func-in-class %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: }
diff --git a/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class_noAttr.mlir b/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class_noAttr.mlir
deleted file mode 100644
index 92ed20c4b14e3..0000000000000
--- a/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class_noAttr.mlir
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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: }
diff --git a/mlir/test/Target/Cpp/class.mlir b/mlir/test/Target/Cpp/class.mlir
new file mode 100644
index 0000000000000..6e777ea183268
--- /dev/null
+++ b/mlir/test/Target/Cpp/class.mlir
@@ -0,0 +1,78 @@
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s
+
+emitc.class @modelClass {
+  emitc.field @fieldName0 : !emitc.array<1xf32>
+  emitc.field @fieldName1 : !emitc.array<1xf32>
+  emitc.func @execute() {
+    %0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
+    %1 = get_field @fieldName0 : !emitc.array<1xf32>
+    %2 = get_field @fieldName1 : !emitc.array<1xf32>
+    %3 = subscript %1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
+    return
+  }
+}
+
+// CHECK-LABEL: class modelClass {
+// CHECK-NEXT:   public:
+// CHECK-NEXT:    float fieldName0[1];
+// CHECK-NEXT:    float fieldName1[1];
+// CHECK-NEXT:    void execute() {
+// CHECK-NEXT:     size_t v1 = 0;
+// CHECK-NEXT:     return;
+// CHECK-NEXT:    }
+// CHECK-NEXT:  };
+
+emitc.class final @finalClass {
+  emitc.field @fieldName0 : !emitc.array<1xf32>
+  emitc.field @fieldName1 : !emitc.array<1xf32>
+  emitc.func @execute() {
+    %0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
+    %1 = get_field @fieldName0 : !emitc.array<1xf32>
+    %2 = get_field @fieldName1 : !emitc.array<1xf32>
+    %3 = subscript %1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
+    return
+  }
+}
+
+// CHECK-LABEL: class finalClass final {
+// CHECK-NEXT:   public:
+// CHECK-NEXT:    float fieldName0[1];
+// CHECK-NEXT:    float fieldName1[1];
+// CHECK-NEXT:    void execute() {
+// CHECK-NEXT:     size_t v1 = 0;
+// CHECK-NEXT:     return;
+// CHECK-NEXT:    }
+// CHECK-NEXT:  };
+
+emitc.class @mainClass {
+  emitc.field @fieldName0 : !emitc.array<2xf32> = dense<0.0> {attrs = {emitc.name_hint = "another_feature"}}
+  emitc.func @get_fieldName0() {
+    %0 = emitc.get_field @fieldName0 : !emitc.array<2xf32>
+    return 
+  }
+}
+
+// CHECK-LABEL: class mainClass {
+// CHECK-NEXT:   public:
+// CHECK-NEXT:    float fieldName0[2] = {0.0e+00f, 0.0e+00f};
+// CHECK-NEXT:    void get_fieldName0() {
+// CHECK-NEXT:     return;
+// CHECK-NEXT:    }
+// CHECK-NEXT:  };
+
+emitc.class @reflectionClass {
+  emitc.field @reflectionMap : !emitc.opaque<"const std::map<std::string, std::string>"> = #emitc.opaque<"{ { \22another_feature\22, \22fieldName0\22 } }"> 
+  emitc.func @get_reflectionMap() {
+    %0 = emitc.get_field @reflectionMap : !emitc.opaque<"const std::map<std::string, std::string>">
+    return 
+  }
+}
+
+// CHECK-LABEL: class reflectionClass {
+// CHECK-NEXT:   public:
+// CHECK-NEXT:    const std::map<std::string, std::string> reflectionMap = { { "another_feature", "fieldName0" } };
+// CHECK-NEXT:    void get_reflectionMap() {
+// CHECK-NEXT:     return;
+// CHECK-NEXT:    }
+// CHECK-NEXT:  };
+
diff --git a/mlir/test/mlir-translate/emitc_classops.mlir b/mlir/test/mlir-translate/emitc_classops.mlir
deleted file mode 100644
index d880f9b16dfc6..0000000000000
--- a/mlir/test/mlir-translate/emitc_classops.mlir
+++ /dev/null
@@ -1,78 +0,0 @@
-// RUN: mlir-translate --mlir-to-cpp %s | FileCheck %s
-
-emitc.class @modelClass {
-    emitc.field @fieldName0 : !emitc.array<1xf32> 
-    emitc.field @fieldName1 : !emitc.array<1xf32> 
-    emitc.func @execute() {
-        %0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
-        %1 = get_field @fieldName0 : !emitc.array<1xf32>
-        %2 = get_field @fieldName1 : !emitc.array<1xf32>
-        %3 = subscript %1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
-        return
-    }
-}
-
-// CHECK-LABEL: class modelClass {
-// CHECK-NEXT: public:
-// CHECK-NEXT:  float fieldName0[1];
-// CHECK-NEXT:  float fieldName1[1];
-// CHECK-NEXT:  void execute() {
-// CHECK-NEXT:    size_t v1 = 0;
-// CHECK-NEXT:    return;
-// CHECK-NEXT:  }
-// CHECK-NEXT: };
-
-emitc.class final @finalClass {
-    emitc.field @fieldName0 : !emitc.array<1xf32> 
-    emitc.field @fieldName1 : !emitc.array<1xf32> 
-    emitc.func @execute() {
-        %0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
-        %1 = get_field @fieldName0 : !emitc.array<1xf32>
-        %2 = get_field @fieldName1 : !emitc.array<1xf32>
-        %3 = subscript %1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
-        return
-    }
-}
-
-// CHECK-LABEL: class finalClass final {
-// CHECK-NEXT: public:
-// CHECK-NEXT:  float fieldName0[1];
-// CHECK-NEXT:  float fieldName1[1];
-// CHECK-NEXT:  void execute() {
-// CHECK-NEXT:    size_t v1 = 0;
-// CHECK-NEXT:    return;
-// CHECK-NEXT:  }
-// CHECK-NEXT: };
-
-emitc.class @mainClass {
-  emitc.field @fieldName0 : !emitc.array<2xf32> = dense<0.0> {attrs = {emitc.name_hint = "another_feature"}}
-  emitc.func @get_fieldName0() {
-    %0 = emitc.get_field @fieldName0 : !emitc.array<2xf32>
-    return 
-  }
-}
-
-// CHECK-LABEL: class mainClass {
-// CHECK-NEXT: public:
-// CHECK-NEXT:  float fieldName0[2] = {0.0e+00f, 0.0e+00f};
-// CHECK-NEXT:  void get_fieldName0() {
-// CHECK-NEXT:    return;
-// CHECK-NEXT:  }
-// CHECK-NEXT: };
-
-emitc.class @reflectionClass {
-  emitc.field @reflectionMap : !emitc.opaque<"const std::map<std::string, std::string>"> = #emitc.opaque<"{ { \22another_feature\22, \22fieldName0\22 } }"> 
-  emitc.func @get_reflectionMap() {
-    %0 = emitc.get_field @reflectionMap : !emitc.opaque<"const std::map<std::string, std::string>">
-    return 
-  }
-}
-
-// CHECK-LABEL: class reflectionClass {
-// CHECK-NEXT: public:
-// CHECK-NEXT:  const std::map<std::string, std::string> reflectionMap = { { "another_feature", "fieldName0" } };
-// CHECK-NEXT:  void get_reflectionMap() {
-// CHECK-NEXT:    return;
-// CHECK-NEXT:  }
-// CHECK-NEXT: };
-



More information about the Mlir-commits mailing list