[NFC] Cleanup EmitC Conversion (#11793)

This moves more common code into `EmitCBuilders` and removes unused
arguments from multiple helper functions.
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp
index ba6c0da..25c838c 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp
@@ -41,6 +41,12 @@
   SHIM_ARGUMENT_MODULE_STATE,
 };
 
+enum {
+  CCONV_ARGUMENT_STACK = 0,
+  CCONV_ARGUMENT_MODULE,
+  CCONV_ARGUMENT_MODULE_STATE,
+};
+
 /// The EmitC dialect is currently missing operations to cleanly represent some
 /// constructs we need for the C target. This includes storage class specifiers
 /// on functions, forward declarations of functions, globals and arrays.
@@ -50,63 +56,19 @@
   op->setAttr(name, value);
 }
 
-/// Create a call to the sizeof operator with `value` as operand.
-Value callSizeof(OpBuilder builder, Location loc, Value value) {
-  auto ctx = builder.getContext();
-  return builder
-      .create<emitc::CallOp>(
-          /*location=*/loc,
-          /*type=*/emitc::OpaqueType::get(ctx, "iree_host_size_t"),
-          /*callee=*/StringAttr::get(ctx, "sizeof"),
-          /*args=*/ArrayAttr{},
-          /*templateArgs=*/ArrayAttr{},
-          /*operands=*/ArrayRef<Value>{value})
-      .getResult(0);
-}
-
-/// Create a call to the sizeof operator with `attr` as operand.
-Value callSizeof(OpBuilder builder, Location loc, Attribute attr) {
-  auto ctx = builder.getContext();
-  return builder
-      .create<emitc::CallOp>(
-          /*location=*/loc,
-          /*type=*/emitc::OpaqueType::get(ctx, "iree_host_size_t"),
-          /*callee=*/StringAttr::get(ctx, "sizeof"),
-          /*args=*/ArrayAttr::get(ctx, {attr}),
-          /*templateArgs=*/ArrayAttr{},
-          /*operands=*/ArrayRef<Value>{})
-      .getResult(0);
-}
-
 /// Create a call to memset to clear a struct
 LogicalResult clearStruct(OpBuilder builder, Value structValue) {
-  auto ctx = structValue.getContext();
   auto loc = structValue.getLoc();
 
-  Value structPointerValue;
-  Value sizeValue;
-
   if (auto ptrType = structValue.getType().dyn_cast<emitc::PointerType>()) {
-    structPointerValue = structValue;
-    sizeValue = callSizeof(builder, loc, TypeAttr::get(ptrType.getPointee()));
-  } else {
-    structPointerValue = emitc_builders::addressOf(builder, loc, structValue);
-    sizeValue = callSizeof(builder, loc, structValue);
+    Value sizeValue = emitc_builders::sizeOf(
+        builder, loc, TypeAttr::get(ptrType.getPointee()));
+    emitc_builders::memset(builder, loc, structValue, 0, sizeValue);
+
+    return success();
   }
 
-  builder.create<emitc::CallOp>(
-      /*location=*/loc,
-      /*type=*/TypeRange{},
-      /*callee=*/StringAttr::get(ctx, "memset"),
-      /*args=*/
-      ArrayAttr::get(ctx,
-                     {builder.getIndexAttr(0), builder.getUI32IntegerAttr(0),
-                      builder.getIndexAttr(1)}),
-      /*templateArgs=*/ArrayAttr{},
-      /*operands=*/
-      ArrayRef<Value>{structPointerValue, sizeValue});
-
-  return success();
+  return emitError(loc, "expected pointer type");
 }
 
 LogicalResult convertFuncOp(IREE::VM::FuncOp funcOp,
@@ -202,12 +164,10 @@
   builder.setInsertionPointToStart(&entryBlock);
 
   for (int i = 0; i < numLocalRefs; i++) {
-    auto refOp = builder.create<emitc::VariableOp>(
-        /*location=*/loc,
-        /*resultType=*/emitc::OpaqueType::get(ctx, "iree_vm_ref_t"),
-        /*value=*/emitc::OpaqueAttr::get(ctx, ""));
+    Value ref = emitc_builders::allocateVariable(
+        builder, loc, emitc::OpaqueType::get(ctx, "iree_vm_ref_t"));
 
-    Value refPtr = emitc_builders::addressOf(builder, loc, refOp.getResult());
+    Value refPtr = emitc_builders::addressOf(builder, loc, ref);
     auto refPtrOp = cast<emitc::ApplyOp>(refPtr.getDefiningOp());
 
     // Cache local refs so that we can release them before a return operation.
@@ -330,13 +290,13 @@
       {IREE::VM::OpaqueType::get(ctx),
        {"IREE_VM_VALUE_TYPE_NONE", "IREE_VM_REF_TYPE_NULL"}},
   };
+  Value elementTypeValue = emitc_builders::allocateVariable(
+      rewriter, loc, emitc::OpaqueType::get(ctx, "iree_vm_type_def_t"));
 
-  auto elementTypeOp = rewriter.create<emitc::VariableOp>(
-      /*location=*/loc,
-      /*resultType=*/emitc::OpaqueType::get(ctx, "iree_vm_type_def_t"),
-      /*value=*/emitc::OpaqueAttr::get(ctx, ""));
+  Value elementTypePtr =
+      emitc_builders::addressOf(rewriter, loc, elementTypeValue);
 
-  if (failed(clearStruct(rewriter, elementTypeOp.getResult()))) {
+  if (failed(clearStruct(rewriter, elementTypePtr))) {
     return std::nullopt;
   }
 
@@ -344,12 +304,12 @@
   if (ptr != valueTypeMap.end()) {
     emitc_builders::structMemberAssign(rewriter, loc,
                                        /*memberName=*/"value_type",
-                                       /*operand=*/elementTypeOp.getResult(),
+                                       /*operand=*/elementTypeValue,
                                        /*value=*/ptr->second.first);
 
     emitc_builders::structMemberAssign(rewriter, loc,
                                        /*memberName=*/"ref_type",
-                                       /*operand=*/elementTypeOp.getResult(),
+                                       /*operand=*/elementTypeValue,
                                        /*value=*/ptr->second.second);
   } else {
     if (!elementType.isa<IREE::VM::RefType>()) {
@@ -397,13 +357,10 @@
 
     emitc_builders::structMemberAssign(rewriter, loc,
                                        /*memberName=*/"ref_type",
-                                       /*operand=*/elementTypeOp.getResult(),
+                                       /*operand=*/elementTypeValue,
                                        /*value=*/typeDescriptorType);
   }
 
-  Value elementTypePtr =
-      emitc_builders::addressOf(rewriter, loc, elementTypeOp.getResult());
-
   return cast<emitc::ApplyOp>(elementTypePtr.getDefiningOp());
 }
 
@@ -425,13 +382,10 @@
     assert(srcRef.getType() == emitc::PointerType::get(emitc::OpaqueType::get(
                                    ctx, "iree_vm_ref_t")));
 
-    auto tmpRef = builder.create<emitc::VariableOp>(
-        /*location=*/location,
-        /*resultType=*/emitc::OpaqueType::get(ctx, "iree_vm_ref_t"),
-        /*value=*/emitc::OpaqueAttr::get(ctx, ""));
+    Value tmpRef = emitc_builders::allocateVariable(
+        builder, location, emitc::OpaqueType::get(ctx, "iree_vm_ref_t"));
 
-    Value tmpPtr =
-        emitc_builders::addressOf(builder, location, tmpRef.getResult());
+    Value tmpPtr = emitc_builders::addressOf(builder, location, tmpRef);
 
     if (failed(clearStruct(builder, tmpPtr))) {
       return failure();
@@ -507,7 +461,7 @@
 /// `negateCondition` is false.
 emitc::CallOp failableCall(
     OpBuilder &builder, Location location, Type type, StringAttr callee,
-    ArrayAttr args, ArrayAttr templateArgs, ArrayRef<Value> operands,
+    ArrayAttr args, ArrayRef<Value> operands,
     const std::function<void(emitc::CallOp &)> &failureBlockBuilder,
     bool negateCondition = false) {
   auto callOp = builder.create<emitc::CallOp>(
@@ -515,7 +469,7 @@
       /*type=*/type,
       /*callee=*/callee,
       /*args=*/args,
-      /*templateArgs=*/templateArgs,
+      /*templateArgs=*/ArrayAttr{},
       /*operands=*/operands);
 
   Type boolType = builder.getIntegerType(1);
@@ -554,7 +508,7 @@
 
 emitc::CallOp returnIfError(OpBuilder &builder, Location location,
                             StringAttr callee, ArrayAttr args,
-                            ArrayAttr templateArgs, ArrayRef<Value> operands,
+                            ArrayRef<Value> operands,
                             IREE::VM::EmitCTypeConverter &typeConverter) {
   auto blockBuilder = [&builder, &location,
                        &typeConverter](emitc::CallOp &callOp) {
@@ -568,13 +522,13 @@
 
   auto ctx = builder.getContext();
   Type type = emitc::OpaqueType::get(ctx, "iree_status_t");
-  return failableCall(builder, location, type, callee, args, templateArgs,
-                      operands, blockBuilder, /*negateResult=*/true);
+  return failableCall(builder, location, type, callee, args, operands,
+                      blockBuilder, /*negateCondition=*/true);
 }
 
 emitc::CallOp failListNull(OpBuilder &builder, Location location, Type type,
                            StringAttr callee, ArrayAttr args,
-                           ArrayAttr templateArgs, ArrayRef<Value> operands,
+                           ArrayRef<Value> operands,
                            IREE::VM::EmitCTypeConverter &typeConverter) {
   auto blockBuilder = [&builder, &location,
                        &typeConverter](emitc::CallOp &callOp) {
@@ -598,8 +552,8 @@
     builder.create<mlir::func::ReturnOp>(location, statusOp.getResult(0));
   };
 
-  return failableCall(builder, location, type, callee, args, templateArgs,
-                      operands, blockBuilder);
+  return failableCall(builder, location, type, callee, args, operands,
+                      blockBuilder);
 }
 
 /// Generate a mlir.call op with one result and split the current block into a
@@ -665,7 +619,7 @@
   };
 
   return failableCall(builder, location, callee, operands, blockBuilder,
-                      /*negateResult=*/true);
+                      /*negateCondition=*/true);
 }
 
 LogicalResult createAPIFunctions(IREE::VM::ModuleOp moduleOp,
@@ -678,10 +632,12 @@
 
   std::string moduleName{moduleOp.getName()};
 
-  // destroy
+  // void destroy(void*)
   {
     OpBuilder::InsertionGuard guard(builder);
 
+    const int moduleArgIndex = 0;
+
     auto funcType = mlir::FunctionType::get(
         ctx, {emitc::PointerType::get(emitc::OpaqueType::get(ctx, "void"))},
         {});
@@ -695,6 +651,7 @@
     attachAttribute(funcOp, "emitc.static", UnitAttr::get(ctx));
 
     Block *entryBlock = funcOp.addEntryBlock();
+    const BlockArgument moduleArg = funcOp.getArgument(moduleArgIndex);
 
     builder.setInsertionPointToStart(entryBlock);
 
@@ -704,7 +661,7 @@
         /*location=*/loc,
         /*type=*/
         emitc::PointerType::get(emitc::OpaqueType::get(ctx, moduleTypeName)),
-        /*operand=*/funcOp.getArgument(0));
+        /*operand=*/moduleArg);
 
     auto allocatorOp = emitc_builders::structPtrMember(
         builder, loc,
@@ -723,10 +680,14 @@
     builder.create<mlir::func::ReturnOp>(loc);
   }
 
-  // alloc_state
+  // iree_status_t alloc_state(void*, iree_allocator_t,
+  // iree_vm_module_state_t**)
   {
     OpBuilder::InsertionGuard guard(builder);
 
+    const int allocatorArgIndex = 1;
+    const int moduleStateArgIndex = 2;
+
     auto funcType = mlir::FunctionType::get(
         ctx,
         {emitc::PointerType::get(emitc::OpaqueType::get(ctx, "void")),
@@ -745,22 +706,24 @@
 
     Block *entryBlock = funcOp.addEntryBlock();
 
+    const BlockArgument allocatorArg = funcOp.getArgument(allocatorArgIndex);
+    const BlockArgument moduleStateArg =
+        funcOp.getArgument(moduleStateArgIndex);
+
     builder.setInsertionPointToStart(entryBlock);
 
     std::string moduleStateTypeName = moduleName + "_state_t";
 
-    auto stateOp = builder.create<emitc::VariableOp>(
-        /*location=*/loc,
-        /*resultType=*/
+    Value state = emitc_builders::allocateVariable(
+        builder, loc,
         emitc::PointerType::get(
             emitc::OpaqueType::get(ctx, moduleStateTypeName)),
-        /*value=*/emitc::OpaqueAttr::get(ctx, "NULL"));
+        {"NULL"});
 
-    Value stateSize = callSizeof(
+    Value stateSize = emitc_builders::sizeOf(
         builder, loc, emitc::OpaqueAttr::get(ctx, moduleStateTypeName));
 
-    Value statePtr =
-        emitc_builders::addressOf(builder, loc, stateOp.getResult());
+    Value statePtr = emitc_builders::addressOf(builder, loc, state);
 
     auto voidPtr = builder.create<emitc::CastOp>(
         /*location=*/loc,
@@ -770,26 +733,15 @@
         /*operand=*/statePtr);
 
     returnIfError(builder, loc, StringAttr::get(ctx, "iree_allocator_malloc"),
-                  {}, {},
-                  {funcOp.getArgument(1), stateSize, voidPtr.getResult()},
+                  {}, {allocatorArg, stateSize, voidPtr.getResult()},
                   /*typeConverter=*/typeConverter);
 
-    builder.create<emitc::CallOp>(
-        /*location=*/loc,
-        /*type=*/TypeRange{},
-        /*callee=*/StringAttr::get(ctx, "memset"),
-        /*args=*/
-        ArrayAttr::get(ctx,
-                       {builder.getIndexAttr(0), builder.getUI32IntegerAttr(0),
-                        builder.getIndexAttr(1)}),
-        /*templateArgs=*/ArrayAttr{},
-        /*operands=*/
-        ArrayRef<Value>{stateOp.getResult(), stateSize});
+    emitc_builders::memset(builder, loc, state, 0, stateSize);
 
     emitc_builders::structPtrMemberAssign(builder, loc,
                                           /*memberName=*/"allocator",
-                                          /*operand=*/stateOp.getResult(),
-                                          /*value=*/funcOp.getArgument(1));
+                                          /*operand=*/state,
+                                          /*value=*/allocatorArg);
 
     // Initialize buffers
     for (auto rodataOp : moduleOp.getOps<IREE::VM::RodataOp>()) {
@@ -797,19 +749,18 @@
 
       std::string bufferName = moduleName + "_" + rodataOp.getName().str();
 
-      auto rodataPointer = builder.create<emitc::VariableOp>(
-          /*location=*/loc,
-          /*resultType=*/
+      Value rodataPointer = emitc_builders::allocateVariable(
+          builder, loc,
           emitc::PointerType::get(emitc::OpaqueType::get(ctx, "const uint8_t")),
-          /*value=*/emitc::OpaqueAttr::get(ctx, bufferName));
+          {bufferName});
 
       auto bufferVoid = builder.create<emitc::CastOp>(
           /*location=*/loc,
           /*type=*/emitc::PointerType::get(emitc::OpaqueType::get(ctx, "void")),
-          /*operand=*/rodataPointer.getResult());
+          /*operand=*/rodataPointer);
 
-      Value bufferSize =
-          callSizeof(builder, loc, emitc::OpaqueAttr::get(ctx, bufferName));
+      Value bufferSize = emitc_builders::sizeOf(
+          builder, loc, emitc::OpaqueAttr::get(ctx, bufferName));
 
       auto byteSpan = builder.create<emitc::CallOp>(
           /*location=*/loc,
@@ -835,7 +786,7 @@
           emitc::PointerType::get(
               emitc::OpaqueType::get(ctx, "iree_vm_buffer_t")),
           /*memberName=*/"rodata_buffers",
-          /*operand=*/stateOp.getResult());
+          /*operand=*/state);
 
       auto buffer = emitc_builders::arrayElementAddress(
           builder, loc,
@@ -875,7 +826,7 @@
           /*type=*/
           emitc::PointerType::get(emitc::OpaqueType::get(ctx, "iree_vm_ref_t")),
           /*memberName=*/"refs",
-          /*operand=*/stateOp.getResult());
+          /*operand=*/state);
 
       for (int i = 0; i < numGlobalRefs; i++) {
         auto refPtrOp = emitc_builders::arrayElementAddress(
@@ -897,7 +848,7 @@
         /*type=*/
         emitc::PointerType::get(
             emitc::OpaqueType::get(ctx, "iree_vm_module_state_t")),
-        /*operand=*/stateOp.getResult());
+        /*operand=*/state);
 
     builder.create<emitc::CallOp>(
         /*location=*/loc,
@@ -906,17 +857,19 @@
         /*args=*/ArrayAttr{},
         /*templateArgs=*/ArrayAttr{},
         /*operands=*/
-        ArrayRef<Value>{funcOp.getArgument(2), baseStateOp.getResult()});
+        ArrayRef<Value>{moduleStateArg, baseStateOp.getResult()});
 
     auto status = emitc_builders::ireeOkStatus(builder, loc);
 
     builder.create<mlir::func::ReturnOp>(loc, status);
   }
 
-  // free_state
+  // void free_state(void*, iree_vm_module_state_t*)
   {
     OpBuilder::InsertionGuard guard(builder);
 
+    const int moduleStateArgIndex = 1;
+
     auto funcType = mlir::FunctionType::get(
         ctx,
         {emitc::PointerType::get(emitc::OpaqueType::get(ctx, "void")),
@@ -934,6 +887,9 @@
 
     Block *entryBlock = funcOp.addEntryBlock();
 
+    const BlockArgument moduleStateArg =
+        funcOp.getArgument(moduleStateArgIndex);
+
     builder.setInsertionPointToStart(entryBlock);
 
     std::string moduleStateTypeName = moduleName + "_state_t";
@@ -943,7 +899,7 @@
         /*type=*/
         emitc::PointerType::get(
             emitc::OpaqueType::get(ctx, moduleStateTypeName)),
-        /*operand=*/funcOp.getArgument(1));
+        /*operand=*/moduleStateArg);
 
     // Release refs from state struct.
     auto ordinalCounts = moduleOp.getOrdinalCountsAttr();
@@ -993,10 +949,20 @@
     builder.create<mlir::func::ReturnOp>(loc);
   }
 
-  // resolve_import
+  // iree_status_t resolve_import(
+  //   void*,
+  //   iree_vm_module_state_t*,
+  //   iree_host_size_t,
+  //   const iree_vm_function_t*,
+  //   const iree_vm_function_signature_t*
+  // )
   {
     OpBuilder::InsertionGuard guard(builder);
 
+    const int moduleStateArgIndex = 1;
+    const int ordinalArgIndex = 2;
+    const int functionArgIndex = 3;
+
     auto funcType = mlir::FunctionType::get(
         ctx,
         {
@@ -1021,6 +987,11 @@
 
     Block *entryBlock = funcOp.addEntryBlock();
 
+    const BlockArgument moduleStateArg =
+        funcOp.getArgument(moduleStateArgIndex);
+    const BlockArgument ordinalArg = funcOp.getArgument(ordinalArgIndex);
+    const BlockArgument functionArg = funcOp.getArgument(functionArgIndex);
+
     builder.setInsertionPointToStart(entryBlock);
 
     std::string moduleStateTypeName = moduleName + "_state_t";
@@ -1030,7 +1001,7 @@
         /*type=*/
         emitc::PointerType::get(
             emitc::OpaqueType::get(ctx, moduleStateTypeName)),
-        /*operand=*/funcOp.getArgument(1));
+        /*operand=*/moduleStateArg);
 
     auto imports = emitc_builders::structPtrMember(
         builder, loc,
@@ -1040,16 +1011,11 @@
         /*memberName=*/"imports",
         /*operand=*/stateOp.getResult());
 
-    auto import = builder.create<emitc::CallOp>(
-        /*location=*/loc,
-        /*type=*/
+    auto import = emitc_builders::arrayElementAddress(
+        builder, loc, /*type=*/
         emitc::PointerType::get(
             emitc::OpaqueType::get(ctx, "iree_vm_function_t")),
-        /*callee=*/StringAttr::get(ctx, "EMITC_ARRAY_ELEMENT_ADDRESS"),
-        /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
-        /*operands=*/
-        ArrayRef<Value>{imports, funcOp.getArgument(2)});
+        /*index=*/ordinalArg, /*operand=*/imports);
 
     builder.create<emitc::CallOp>(
         /*location=*/loc,
@@ -1058,17 +1024,25 @@
         /*args=*/ArrayAttr{},
         /*templateArgs=*/ArrayAttr{},
         /*operands=*/
-        ArrayRef<Value>{import.getResult(0), funcOp.getArgument(3)});
+        ArrayRef<Value>{import, functionArg});
 
     auto status = emitc_builders::ireeOkStatus(builder, loc);
 
     builder.create<mlir::func::ReturnOp>(loc, status);
   }
 
-  // create
+  // iree_status_t create(
+  //   iree_vm_instance_t*,
+  //   iree_allocator_t,
+  //   iree_vm_module_t**
+  // );
   {
     OpBuilder::InsertionGuard guard(builder);
 
+    const int instanceArgIndex = 0;
+    const int allocatorArgIndex = 1;
+    const int moduleArgIndex = 2;
+
     auto funcType = mlir::FunctionType::get(
         ctx,
         {
@@ -1099,21 +1073,23 @@
 
     Block *entryBlock = funcOp.addEntryBlock();
 
+    const BlockArgument instanceArg = funcOp.getArgument(instanceArgIndex);
+    const BlockArgument allocatorArg = funcOp.getArgument(allocatorArgIndex);
+    const BlockArgument moduleArg = funcOp.getArgument(moduleArgIndex);
+
     builder.setInsertionPointToStart(entryBlock);
 
     std::string moduleTypeName = moduleName + "_t";
 
-    auto module = builder.create<emitc::VariableOp>(
-        /*location=*/loc,
-        /*resultType=*/
+    Value module = emitc_builders::allocateVariable(
+        builder, loc,
         emitc::PointerType::get(emitc::OpaqueType::get(ctx, moduleTypeName)),
-        /*value=*/emitc::OpaqueAttr::get(ctx, "NULL"));
+        {"NULL"});
 
-    Value moduleSize =
-        callSizeof(builder, loc, emitc::OpaqueAttr::get(ctx, moduleTypeName));
+    Value moduleSize = emitc_builders::sizeOf(
+        builder, loc, emitc::OpaqueAttr::get(ctx, moduleTypeName));
 
-    Value modulePtr =
-        emitc_builders::addressOf(builder, loc, module.getResult());
+    Value modulePtr = emitc_builders::addressOf(builder, loc, module);
 
     auto voidPtr = builder.create<emitc::CastOp>(
         /*location=*/loc,
@@ -1123,34 +1099,20 @@
         /*operand=*/modulePtr);
 
     returnIfError(builder, loc, StringAttr::get(ctx, "iree_allocator_malloc"),
-                  {}, {},
-                  {funcOp.getArgument(1), moduleSize, voidPtr.getResult()},
+                  {}, {allocatorArg, moduleSize, voidPtr.getResult()},
                   /*typeConverter=*/typeConverter);
 
-    builder.create<emitc::CallOp>(
-        /*location=*/loc,
-        /*type=*/TypeRange{},
-        /*callee=*/StringAttr::get(ctx, "memset"),
-        /*args=*/
-        ArrayAttr::get(ctx,
-                       {builder.getIndexAttr(0), builder.getUI32IntegerAttr(0),
-                        builder.getIndexAttr(1)}),
-        /*templateArgs=*/ArrayAttr{},
-        /*operands=*/
-        ArrayRef<Value>{module.getResult(), moduleSize});
+    emitc_builders::memset(builder, loc, module, 0, moduleSize);
 
     emitc_builders::structPtrMemberAssign(builder, loc,
                                           /*memberName=*/"allocator",
-                                          /*operand=*/module.getResult(),
-                                          /*value=*/funcOp.getArgument(1));
+                                          /*operand=*/module,
+                                          /*value=*/allocatorArg);
 
-    auto vmModule = builder.create<emitc::VariableOp>(
-        /*location=*/loc,
-        /*resultType=*/emitc::OpaqueType::get(ctx, "iree_vm_module_t"),
-        /*value=*/emitc::OpaqueAttr::get(ctx, ""));
+    Value vmModule = emitc_builders::allocateVariable(
+        builder, loc, emitc::OpaqueType::get(ctx, "iree_vm_module_t"));
 
-    Value vmModulePtr =
-        emitc_builders::addressOf(builder, loc, vmModule.getResult());
+    Value vmModulePtr = emitc_builders::addressOf(builder, loc, vmModule);
 
     auto vmInitializeStatus = builder.create<emitc::CallOp>(
         /*location=*/loc,
@@ -1159,7 +1121,7 @@
         /*args=*/ArrayAttr{},
         /*templateArgs=*/ArrayAttr{},
         /*operands=*/
-        ArrayRef<Value>{vmModulePtr, module.getResult()});
+        ArrayRef<Value>{vmModulePtr, module});
 
     Type boolType = builder.getIntegerType(1);
 
@@ -1192,7 +1154,7 @@
           /*args=*/ArrayAttr{},
           /*templateArgs=*/ArrayAttr{},
           /*operands=*/
-          ArrayRef<Value>{funcOp.getArgument(1), module.getResult()});
+          ArrayRef<Value>{allocatorArg, module});
 
       builder.create<mlir::func::ReturnOp>(loc,
                                            vmInitializeStatus.getResult(0));
@@ -1210,7 +1172,7 @@
          {"destroy", "alloc_state", "free_state", "resolve_import"}) {
       emitc_builders::structMemberAssign(builder, loc,
                                          /*memberName=*/funcName,
-                                         /*operand=*/vmModule.getResult(),
+                                         /*operand=*/vmModule,
                                          /*value=*/moduleName + "_" + funcName);
     }
 
@@ -1227,8 +1189,7 @@
                              builder.getIndexAttr(3)}),
         /*templateArgs=*/ArrayAttr{},
         /*operands=*/
-        ArrayRef<Value>{vmModulePtr, funcOp.getArgument(0),
-                        funcOp.getArgument(1), funcOp.getArgument(2)});
+        ArrayRef<Value>{vmModulePtr, instanceArg, allocatorArg, moduleArg});
 
     builder.create<mlir::func::ReturnOp>(loc, status.getResult(0));
   }
@@ -1839,14 +1800,9 @@
         /*memberName=*/"module",
         /*operand=*/import);
 
-    auto conditionI1 = builder.create<emitc::CallOp>(
-        /*location=*/location,
-        /*type=*/boolType,
-        /*callee=*/StringAttr::get(ctx, "EMITC_NOT"),
-        /*args=*/
-        ArrayAttr::get(ctx, {builder.getIndexAttr(0)}),
-        /*templateArgs=*/ArrayAttr{},
-        /*operands=*/ArrayRef<Value>{importModule});
+    auto conditionI1 = emitc_builders::unaryOperator(
+        builder, location, emitc_builders::UnaryOperator::LOGICAL_NOT,
+        importModule, boolType);
 
     // Start by splitting the block into two. The part before will contain the
     // condition, and the part after will contain the continuation point.
@@ -1878,8 +1834,8 @@
     }
 
     builder.setInsertionPointToEnd(condBlock);
-    builder.create<IREE::VM::CondBranchOp>(location, conditionI1.getResult(0),
-                                           failureBlock, continuationBlock);
+    builder.create<IREE::VM::CondBranchOp>(location, conditionI1, failureBlock,
+                                           continuationBlock);
 
     builder.setInsertionPointToStart(continuationBlock);
   }
@@ -1939,7 +1895,8 @@
                << "Failed to build size expressions for call struct";
       }
 
-      auto importArg = newFuncOp.getArgument(1);
+      const int importArgIndex = 1;
+      const BlockArgument importArg = newFuncOp.getArgument(importArgIndex);
       failIfImportUnresolved(builder, loc, importArg);
 
       auto call = buildIreeVmFunctionCallStruct(
@@ -1955,7 +1912,8 @@
         return importOp.emitError() << "failed to pack argument struct";
       }
 
-      auto stackArg = newFuncOp.getArgument(0);
+      const BlockArgument stackArg =
+          newFuncOp.getArgument(CCONV_ARGUMENT_STACK);
       if (failed(createCall(call.value(), importArg, stackArg, builder, loc))) {
         return importOp.emitError() << "failed to create call";
       }
@@ -2018,17 +1976,12 @@
 
     for (Type type : types) {
       Type valueType = typeConverter.convertTypeAsNonPointer(type);
-      Value size = callSizeof(builder, loc, TypeAttr::get(valueType));
+      Value size =
+          emitc_builders::sizeOf(builder, loc, TypeAttr::get(valueType));
 
-      result = builder
-                   .create<emitc::CallOp>(
-                       /*location=*/loc,
-                       /*type=*/hostSizeType,
-                       /*callee=*/StringAttr::get(ctx, "EMITC_ADD"),
-                       /*args=*/ArrayAttr{},
-                       /*templateArgs=*/ArrayAttr{},
-                       /*operands=*/ArrayRef<Value>{result, size})
-                   .getResult(0);
+      result = emitc_builders::binaryOperator(
+          builder, loc, emitc_builders::BinaryOperator::ADDITION, result, size,
+          hostSizeType);
     }
 
     return {result};
@@ -2120,16 +2073,7 @@
                                           /*value=*/byteSpanData);
 
     // memset(byteSpanData, 0, SIZE);
-    builder.create<emitc::CallOp>(
-        /*location=*/loc,
-        /*type=*/TypeRange{},
-        /*callee=*/StringAttr::get(ctx, "memset"),
-        /*args=*/
-        ArrayAttr::get(ctx,
-                       {builder.getIndexAttr(0), builder.getI32IntegerAttr(0),
-                        builder.getIndexAttr(1)}),
-        /*templateArgs=*/ArrayAttr{},
-        /*operands=*/ArrayRef<Value>{byteSpanData, size});
+    emitc_builders::memset(builder, loc, byteSpanData, 0, size);
 
     return byteSpan;
   }
@@ -2183,33 +2127,22 @@
             /*operands=*/ArrayRef<Value>{arg, refPtr});
       } else {
         assert(!argType.isa<emitc::PointerType>());
-        Value size = callSizeof(builder, loc, TypeAttr::get(argType));
+        Value size =
+            emitc_builders::sizeOf(builder, loc, TypeAttr::get(argType));
 
         // memcpy(uint8Ptr, &arg, size);
         Value argPtr = emitc_builders::addressOf(builder, loc, arg);
-        builder.create<emitc::CallOp>(
-            /*location=*/loc,
-            /*type=*/TypeRange{},
-            /*callee=*/StringAttr::get(ctx, "memcpy"),
-            /*args=*/ArrayAttr{},
-            /*templateArgs=*/ArrayAttr{},
-            /*operands=*/ArrayRef<Value>{uint8Ptr, argPtr, size});
+        emitc_builders::memcpy(builder, loc, uint8Ptr, argPtr, size);
       }
 
       // Skip the addition in the last iteration.
       if (i < inputTypes.size() - 1) {
         Type valueType = typeConverter.convertTypeAsNonPointer(argType);
-        Value size = callSizeof(builder, loc, TypeAttr::get(valueType));
-
-        uint8Ptr = builder
-                       .create<emitc::CallOp>(
-                           /*location=*/loc,
-                           /*type=*/bytePtrType,
-                           /*callee=*/StringAttr::get(ctx, "EMITC_ADD"),
-                           /*args=*/ArrayAttr{},
-                           /*templateArgs=*/ArrayAttr{},
-                           /*operands=*/ArrayRef<Value>{uint8Ptr, size})
-                       .getResult(0);
+        Value size =
+            emitc_builders::sizeOf(builder, loc, TypeAttr::get(valueType));
+        uint8Ptr = emitc_builders::binaryOperator(
+            builder, loc, emitc_builders::BinaryOperator::ADDITION, uint8Ptr,
+            size, bytePtrType);
       }
     }
     return success();
@@ -2265,32 +2198,21 @@
             /*operands=*/ArrayRef<Value>{refPtr, arg});
       } else {
         Type valueType = argType.cast<emitc::PointerType>().getPointee();
-        Value size = callSizeof(builder, loc, TypeAttr::get(valueType));
+        Value size =
+            emitc_builders::sizeOf(builder, loc, TypeAttr::get(valueType));
 
         // memcpy(arg, uint8Ptr, size);
-        builder.create<emitc::CallOp>(
-            /*location=*/loc,
-            /*type=*/TypeRange{},
-            /*callee=*/StringAttr::get(ctx, "memcpy"),
-            /*args=*/ArrayAttr{},
-            /*templateArgs=*/ArrayAttr{},
-            /*operands=*/ArrayRef<Value>{arg, uint8Ptr, size});
+        emitc_builders::memcpy(builder, loc, arg, uint8Ptr, size);
       }
 
       // Skip the addition in the last iteration.
       if (i < resultTypes.size() - 1) {
         Type valueType = argType.cast<emitc::PointerType>().getPointee();
-        Value size = callSizeof(builder, loc, TypeAttr::get(valueType));
-
-        uint8Ptr = builder
-                       .create<emitc::CallOp>(
-                           /*location=*/loc,
-                           /*type=*/bytePtrType,
-                           /*callee=*/StringAttr::get(ctx, "EMITC_ADD"),
-                           /*args=*/ArrayAttr{},
-                           /*templateArgs=*/ArrayAttr{},
-                           /*operands=*/ArrayRef<Value>{uint8Ptr, size})
-                       .getResult(0);
+        Value size =
+            emitc_builders::sizeOf(builder, loc, TypeAttr::get(valueType));
+        uint8Ptr = emitc_builders::binaryOperator(
+            builder, loc, emitc_builders::BinaryOperator::ADDITION, uint8Ptr,
+            size, bytePtrType);
       }
     }
     return success();
@@ -2322,9 +2244,7 @@
                            builder.getIndexAttr(1),
                            builder.getIndexAttr(2),
                        }),
-        /*templateArgs=*/ArrayAttr{},
-        /*operands=*/
-        ArrayRef<Value>{importModule, stack, call},
+        /*operands=*/ArrayRef<Value>{importModule, stack, call},
         /*typeConverter=*/typeConverter);
 
     return success();
@@ -2429,9 +2349,12 @@
 
     auto parentFuncOp = op->getParentOfType<mlir::func::FuncOp>();
 
-    BlockArgument stackArg = parentFuncOp.getArgument(0);
-    BlockArgument moduleArg = parentFuncOp.getArgument(1);
-    BlockArgument moduleStateArg = parentFuncOp.getArgument(2);
+    const BlockArgument stackArg =
+        parentFuncOp.getArgument(CCONV_ARGUMENT_STACK);
+    const BlockArgument moduleArg =
+        parentFuncOp.getArgument(CCONV_ARGUMENT_MODULE);
+    const BlockArgument moduleStateArg =
+        parentFuncOp.getArgument(CCONV_ARGUMENT_MODULE_STATE);
 
     updatedOperands = {stackArg, moduleArg, moduleStateArg};
 
@@ -2470,8 +2393,10 @@
     int importOrdinal = importOp.getOrdinal()->getZExtValue();
 
     auto funcOp = op->getParentOfType<mlir::func::FuncOp>();
-    BlockArgument stackArg = funcOp.getArgument(0);
-    BlockArgument stateArg = funcOp.getArgument(2);
+
+    const BlockArgument stackArg = funcOp.getArgument(CCONV_ARGUMENT_STACK);
+    const BlockArgument stateArg =
+        funcOp.getArgument(CCONV_ARGUMENT_MODULE_STATE);
 
     auto imports = emitc_builders::structPtrMember(
         rewriter, loc,
@@ -2530,7 +2455,6 @@
                                ConversionPatternRewriter &rewriter,
                                SmallVector<Value, 4> &updatedOperands,
                                SmallVector<Value, 4> &resultOperands) const {
-    auto ctx = op->getContext();
     auto loc = op->getLoc();
 
     OperandRange operands = op->getOperands();
@@ -2592,15 +2516,11 @@
         resultOperands.push_back(ref.value());
         updatedOperands.push_back(ref.value());
       } else {
-        auto resultOp = rewriter.create<emitc::VariableOp>(
-            /*location=*/loc,
-            /*resultType=*/result.getType(),
-            /*value=*/emitc::OpaqueAttr::get(ctx, ""));
+        Value resultValue =
+            emitc_builders::allocateVariable(rewriter, loc, result.getType());
+        Value resultPtr = emitc_builders::addressOf(rewriter, loc, resultValue);
 
-        Value resultPtr =
-            emitc_builders::addressOf(rewriter, loc, resultOp.getResult());
-
-        resultOperands.push_back(resultOp.getResult());
+        resultOperands.push_back(resultValue);
         updatedOperands.push_back(resultPtr);
       }
     }
@@ -2858,7 +2778,9 @@
     auto funcOp =
         constRefRodataOp.getOperation()->getParentOfType<mlir::func::FuncOp>();
 
-    BlockArgument stateArg = funcOp.getArgument(2);
+    const BlockArgument stateArg =
+        funcOp.getArgument(CCONV_ARGUMENT_MODULE_STATE);
+
     auto rodataBuffersPtr = emitc_builders::structPtrMember(
         rewriter, loc,
         /*type=*/
@@ -2900,7 +2822,6 @@
         /*location=*/loc,
         /*callee=*/StringAttr::get(ctx, "iree_vm_ref_wrap_retain"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/
         ArrayRef<Value>{byteBufferPtrOp, typeIdOp.getResult(0), ref.value()},
         /*typeConverter=*/*typeConverter);
@@ -3182,7 +3103,9 @@
     int importOrdinal = importOp.getOrdinal()->getZExtValue();
 
     auto funcOp = op->getParentOfType<mlir::func::FuncOp>();
-    BlockArgument stateArg = funcOp.getArgument(2);
+
+    const BlockArgument stateArg =
+        funcOp.getArgument(CCONV_ARGUMENT_MODULE_STATE);
 
     auto imports = emitc_builders::structPtrMember(
         rewriter, loc,
@@ -3210,27 +3133,12 @@
         /*operand=*/import);
 
     Type boolType = rewriter.getIntegerType(1);
-    auto conditionI1 = rewriter
-                           .create<emitc::CallOp>(
-                               /*location=*/loc,
-                               /*type=*/boolType,
-                               /*callee=*/StringAttr::get(ctx, "EMITC_NOT"),
-                               /*args=*/
-                               ArrayAttr::get(ctx, {rewriter.getIndexAttr(0)}),
-                               /*templateArgs=*/ArrayAttr{},
-                               /*operands=*/ArrayRef<Value>{importModule})
-                           .getResult(0);
-    auto invConditionI1 =
-        rewriter
-            .create<emitc::CallOp>(
-                /*location=*/loc,
-                /*type=*/boolType,
-                /*callee=*/StringAttr::get(ctx, "EMITC_NOT"),
-                /*args=*/
-                ArrayAttr::get(ctx, {rewriter.getIndexAttr(0)}),
-                /*templateArgs=*/ArrayAttr{},
-                /*operands=*/ArrayRef<Value>{conditionI1})
-            .getResult(0);
+    auto conditionI1 = emitc_builders::unaryOperator(
+        rewriter, loc, emitc_builders::UnaryOperator::LOGICAL_NOT, importModule,
+        boolType);
+    auto invConditionI1 = emitc_builders::unaryOperator(
+        rewriter, loc, emitc_builders::UnaryOperator::LOGICAL_NOT, conditionI1,
+        boolType);
 
     auto i32Type = rewriter.getIntegerType(32);
     auto conditionI32 = rewriter.create<emitc::CastOp>(
@@ -3373,7 +3281,9 @@
     auto funcOp =
         loadOp.getOperation()->template getParentOfType<mlir::func::FuncOp>();
 
-    BlockArgument stateArg = funcOp.getArgument(2);
+    const BlockArgument stateArg =
+        funcOp.getArgument(CCONV_ARGUMENT_MODULE_STATE);
+
     auto rwDataPtr = emitc_builders::structPtrMember(
         rewriter, loc,
         /*type=*/emitc::PointerType::get(rewriter.getIntegerType(8, false)),
@@ -3448,7 +3358,9 @@
       return op->emitError() << "local ref not found";
     }
 
-    BlockArgument stateArg = funcOp.getArgument(2);
+    const BlockArgument stateArg =
+        funcOp.getArgument(CCONV_ARGUMENT_MODULE_STATE);
+
     auto refs = emitc_builders::structPtrMember(
         rewriter, loc,
         /*type=*/
@@ -3490,9 +3402,7 @@
         ArrayAttr::get(ctx,
                        {rewriter.getBoolAttr(move), rewriter.getIndexAttr(0),
                         rewriter.getIndexAttr(1), rewriter.getIndexAttr(2)}),
-        /*templateArgs=*/ArrayAttr{},
-        /*operands=*/
-        ArrayRef<Value>{srcRef, typedefRefType, destRef},
+        /*operands=*/ArrayRef<Value>{srcRef, typedefRefType, destRef},
         /*typeConverter=*/*typeConverter);
 
     if (isLoad) {
@@ -3532,7 +3442,9 @@
     auto funcOp =
         storeOp.getOperation()->template getParentOfType<mlir::func::FuncOp>();
 
-    BlockArgument stateArg = funcOp.getArgument(2);
+    const BlockArgument stateArg =
+        funcOp.getArgument(CCONV_ARGUMENT_MODULE_STATE);
+
     auto rwDataPtr = emitc_builders::structPtrMember(
         rewriter, loc,
         /*type=*/emitc::PointerType::get(rewriter.getIntegerType(8, false)),
@@ -3600,7 +3512,6 @@
         emitc::PointerType::get(emitc::OpaqueType::get(ctx, "iree_vm_list_t")),
         /*callee=*/StringAttr::get(ctx, "iree_vm_list_deref"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/ArrayRef<Value>{refValue},
         /*typeConverter=*/*typeConverter);
 
@@ -3621,7 +3532,6 @@
           /*location=*/loc,
           /*callee=*/StringAttr::get(ctx, funcName),
           /*args=*/ArrayAttr{},
-          /*templateArgs=*/ArrayAttr{},
           /*operands=*/ArrayRef<Value>(updatedOperands),
           /*typeConverter=*/*typeConverter);
 
@@ -3678,20 +3588,20 @@
       return allocOp.emitError() << "generating iree_vm_type_def_t* failed";
     }
 
-    auto listOp = rewriter.create<emitc::VariableOp>(
-        /*location=*/loc,
-        /*resultType=*/
+    Value list = emitc_builders::allocateVariable(
+        rewriter, loc,
         emitc::PointerType::get(emitc::OpaqueType::get(ctx, "iree_vm_list_t")),
-        /*value=*/emitc::OpaqueAttr::get(ctx, "NULL"));
+        {"NULL"});
 
-    Value listPtr =
-        emitc_builders::addressOf(rewriter, loc, listOp.getResult());
+    Value listPtr = emitc_builders::addressOf(rewriter, loc, list);
 
     auto funcOp = allocOp.getOperation()->getParentOfType<mlir::func::FuncOp>();
     IREE::VM::EmitCTypeConverter *typeConverter =
         getTypeConverter<IREE::VM::EmitCTypeConverter>();
 
-    BlockArgument stateArg = funcOp.getArgument(2);
+    const BlockArgument stateArg =
+        funcOp.getArgument(CCONV_ARGUMENT_MODULE_STATE);
+
     auto allocatorOp = emitc_builders::structPtrMember(
         rewriter, loc,
         /*type=*/emitc::OpaqueType::get(ctx, "iree_allocator_t"),
@@ -3703,7 +3613,6 @@
         /*location=*/loc,
         /*callee=*/StringAttr::get(ctx, "iree_vm_list_create"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/
         ArrayRef<Value>{elementTypePtrOp.value().getResult(),
                         adaptor.getOperands()[0], allocatorOp, listPtr},
@@ -3728,10 +3637,8 @@
         /*location=*/loc,
         /*callee=*/StringAttr::get(ctx, "iree_vm_ref_wrap_assign"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/
-        ArrayRef<Value>{listOp.getResult(), refTypeOp.getResult(0),
-                        ref.value()},
+        ArrayRef<Value>{list, refTypeOp.getResult(0), ref.value()},
         /*typeConverter=*/*typeConverter);
 
     rewriter.replaceOp(allocOp, ref.value());
@@ -3774,13 +3681,10 @@
       return getOp.emitOpError() << "element type not handled";
     }
 
-    auto valueOp = rewriter.create<emitc::VariableOp>(
-        /*location=*/loc,
-        /*resultType=*/emitc::OpaqueType::get(ctx, "iree_vm_value_t"),
-        /*value=*/emitc::OpaqueAttr::get(ctx, ""));
+    Value value = emitc_builders::allocateVariable(
+        rewriter, loc, emitc::OpaqueType::get(ctx, "iree_vm_value_t"));
 
-    Value valuePtr =
-        emitc_builders::addressOf(rewriter, loc, valueOp.getResult());
+    Value valuePtr = emitc_builders::addressOf(rewriter, loc, value);
 
     Value refValue =
         emitc_builders::contentsOf(rewriter, loc, adaptor.getOperands()[0]);
@@ -3795,7 +3699,6 @@
         emitc::PointerType::get(emitc::OpaqueType::get(ctx, "iree_vm_list_t")),
         /*callee=*/StringAttr::get(ctx, "iree_vm_list_deref"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/ArrayRef<Value>{refValue},
         /*typeConverter=*/*typeConverter);
 
@@ -3807,7 +3710,6 @@
         ArrayAttr::get(ctx, {rewriter.getIndexAttr(0), rewriter.getIndexAttr(1),
                              emitc::OpaqueAttr::get(ctx, valueTypeEnum.value()),
                              rewriter.getIndexAttr(2)}),
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/
         ArrayRef<Value>{listDerefOp.getResult(0), getOp.getIndex(), valuePtr},
         /*typeConverter=*/*typeConverter);
@@ -3848,7 +3750,6 @@
         emitc::PointerType::get(emitc::OpaqueType::get(ctx, "iree_vm_list_t")),
         /*callee=*/StringAttr::get(ctx, "iree_vm_list_deref"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/ArrayRef<Value>{listRefValue},
         /*typeConverter=*/*typeConverter);
 
@@ -3863,7 +3764,6 @@
         /*location=*/loc,
         /*callee=*/StringAttr::get(ctx, "iree_vm_list_get_ref_retain"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/
         ArrayRef<Value>{listDerefOp.getResult(0), getOp.getIndex(),
                         ref.value()},
@@ -3882,7 +3782,7 @@
     // (ref->type != IREE_VM_REF_TYPE_NULL &&
     // (iree_vm_type_def_is_value(type_def) || ref->type !=
     // type_def->ref_type))
-    emitc::CallOp invalidType;
+    Value invalidType;
     {
       auto refType = emitc_builders::structPtrMember(
           rewriter, loc,
@@ -3910,43 +3810,20 @@
           /*memberName=*/"ref_type",
           /*operand=*/elementTypePtrOp.value().getResult());
 
-      auto refTypeIsNotNull = rewriter.create<emitc::CallOp>(
-          /*location=*/loc,
-          /*type=*/rewriter.getI1Type(),
-          /*callee=*/StringAttr::get(ctx, "EMITC_NE"),
-          /*args=*/ArrayAttr{},
-          /*templateArgs=*/ArrayAttr{},
-          /*operands=*/
-          ArrayRef<Value>{refType, refTypeNull.getResult()});
+      auto refTypeIsNotNull = emitc_builders::binaryOperator(
+          rewriter, loc, emitc_builders::BinaryOperator::NOT_EQUAL_TO, refType,
+          refTypeNull.getResult(), rewriter.getI1Type());
+      auto refTypesDontMatch = emitc_builders::binaryOperator(
+          rewriter, loc, emitc_builders::BinaryOperator::NOT_EQUAL_TO, refType,
+          typedefRefType, rewriter.getI1Type());
 
-      auto refTypesDontMatch = rewriter.create<emitc::CallOp>(
-          /*location=*/loc,
-          /*type=*/rewriter.getI1Type(),
-          /*callee=*/StringAttr::get(ctx, "EMITC_NE"),
-          /*args=*/ArrayAttr{},
-          /*templateArgs=*/ArrayAttr{},
-          /*operands=*/
-          ArrayRef<Value>{refType, typedefRefType});
+      auto invalidRefType = emitc_builders::binaryOperator(
+          rewriter, loc, emitc_builders::BinaryOperator::LOGICAL_OR,
+          typedefIsValue.getResult(0), refTypesDontMatch, rewriter.getI1Type());
 
-      auto invalidRefType = rewriter.create<emitc::CallOp>(
-          /*location=*/loc,
-          /*type=*/rewriter.getI1Type(),
-          /*callee=*/StringAttr::get(ctx, "EMITC_OR"),
-          /*args=*/ArrayAttr{},
-          /*templateArgs=*/ArrayAttr{},
-          /*operands=*/
-          ArrayRef<Value>{typedefIsValue.getResult(0),
-                          refTypesDontMatch.getResult(0)});
-
-      invalidType = rewriter.create<emitc::CallOp>(
-          /*location=*/loc,
-          /*type=*/rewriter.getI1Type(),
-          /*callee=*/StringAttr::get(ctx, "EMITC_AND"),
-          /*args=*/ArrayAttr{},
-          /*templateArgs=*/ArrayAttr{},
-          /*operands=*/
-          ArrayRef<Value>{refTypeIsNotNull.getResult(0),
-                          invalidRefType.getResult(0)});
+      invalidType = emitc_builders::binaryOperator(
+          rewriter, loc, emitc_builders::BinaryOperator::LOGICAL_AND,
+          refTypeIsNotNull, invalidRefType, rewriter.getI1Type());
     }
 
     // Start by splitting the block into two. The part before will contain
@@ -3969,8 +3846,8 @@
     }
 
     rewriter.setInsertionPointToEnd(condBlock);
-    rewriter.create<IREE::VM::CondBranchOp>(loc, invalidType.getResult(0),
-                                            failureBlock, continuationBlock);
+    rewriter.create<IREE::VM::CondBranchOp>(loc, invalidType, failureBlock,
+                                            continuationBlock);
 
     rewriter.replaceOp(getOp, ref.value());
 
@@ -4025,7 +3902,6 @@
         emitc::PointerType::get(emitc::OpaqueType::get(ctx, "iree_vm_list_t")),
         /*callee=*/StringAttr::get(ctx, "iree_vm_list_deref"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/ArrayRef<Value>{refValue},
         /*typeConverter=*/*typeConverter);
 
@@ -4034,7 +3910,6 @@
         /*location=*/loc,
         /*callee=*/StringAttr::get(ctx, "iree_vm_list_set_value"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/
         ArrayRef<Value>{listDerefOp.getResult(0), setOp.getIndex(), valuePtr},
         /*typeConverter=*/*typeConverter);
@@ -4069,7 +3944,6 @@
         emitc::PointerType::get(emitc::OpaqueType::get(ctx, "iree_vm_list_t")),
         /*callee=*/StringAttr::get(ctx, "iree_vm_list_deref"),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/ArrayRef<Value>{refValue},
         /*typeConverter=*/*typeConverter);
 
@@ -4090,7 +3964,6 @@
         /*location=*/loc,
         /*callee=*/StringAttr::get(ctx, callee),
         /*args=*/ArrayAttr{},
-        /*templateArgs=*/ArrayAttr{},
         /*operands=*/
         ArrayRef<Value>{listDerefOp.getResult(0), setOp.getIndex(),
                         adaptor.getValue()},
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/EmitCBuilders.cpp b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/EmitCBuilders.cpp
index 5362de3..ea51344 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/EmitCBuilders.cpp
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/EmitCBuilders.cpp
@@ -13,6 +13,109 @@
 namespace iree_compiler {
 namespace emitc_builders {
 
+namespace {
+std::string mapUnaryOperator(UnaryOperator op) {
+  switch (op) {
+    case UnaryOperator::PLUS:
+      return "+";
+    case UnaryOperator::MINUS:
+      return "-";
+    case UnaryOperator::BITWISE_NOT:
+      return "~";
+    case UnaryOperator::LOGICAL_NOT:
+      return "!";
+  }
+}
+
+std::string mapBinaryOperator(BinaryOperator op) {
+  switch (op) {
+    case BinaryOperator::ADDITION:
+      return "+";
+    case BinaryOperator::SUBTRACTION:
+      return "-";
+    case BinaryOperator::PRODUCT:
+      return "*";
+    case BinaryOperator::DIVISION:
+      return "/";
+    case BinaryOperator::REMAINDER:
+      return "%";
+    case BinaryOperator::BITWISE_AND:
+      return "&";
+    case BinaryOperator::BITWISE_OR:
+      return "|";
+    case BinaryOperator::BITWISE_XOR:
+      return "^";
+    case BinaryOperator::BITWISE_LEFT_SHIFT:
+      return "<<";
+    case BinaryOperator::BITWISE_RIGHT_SHIFT:
+      return ">>";
+    case BinaryOperator::LOGICAL_AND:
+      return "&&";
+    case BinaryOperator::LOGICAL_OR:
+      return "||";
+    case BinaryOperator::EQUAL_TO:
+      return "==";
+    case BinaryOperator::NOT_EQUAL_TO:
+      return "!=";
+    case BinaryOperator::LESS_THAN:
+      return "<";
+    case BinaryOperator::GREATER_THAN:
+      return ">";
+    case BinaryOperator::LESS_THAN_OR_EQUAL:
+      return "<=";
+    case BinaryOperator::GREATER_THAN_OR_EQUAL:
+      return ">=";
+  }
+}
+}  // namespace
+
+Value unaryOperator(OpBuilder builder, Location location, UnaryOperator op,
+                    Value operand, Type resultType) {
+  auto ctx = builder.getContext();
+
+  return builder
+      .create<emitc::CallOp>(
+          /*location=*/location,
+          /*type=*/resultType,
+          /*callee=*/StringAttr::get(ctx, "EMITC_UNARY"),
+          /*args=*/
+          ArrayAttr::get(ctx,
+                         {emitc::OpaqueAttr::get(ctx, mapUnaryOperator(op)),
+                          builder.getIndexAttr(0)}),
+          /*templateArgs=*/ArrayAttr{},
+          /*operands=*/ArrayRef<Value>{operand})
+      .getResult(0);
+}
+
+Value binaryOperator(OpBuilder builder, Location location, BinaryOperator op,
+                     Value lhs, Value rhs, Type resultType) {
+  auto ctx = builder.getContext();
+
+  return builder
+      .create<emitc::CallOp>(
+          /*location=*/location,
+          /*type=*/resultType,
+          /*callee=*/StringAttr::get(ctx, "EMITC_BINARY"),
+          /*args=*/
+          ArrayAttr::get(ctx,
+                         {emitc::OpaqueAttr::get(ctx, mapBinaryOperator(op)),
+                          builder.getIndexAttr(0), builder.getIndexAttr(1)}),
+          /*templateArgs=*/ArrayAttr{},
+          /*operands=*/ArrayRef<Value>{lhs, rhs})
+      .getResult(0);
+}
+
+Value allocateVariable(OpBuilder builder, Location location, Type type,
+                       Optional<StringRef> initializer) {
+  auto ctx = builder.getContext();
+  return builder
+      .create<emitc::VariableOp>(
+          /*location=*/location,
+          /*resultType=*/type,
+          /*value=*/emitc::OpaqueAttr::get(ctx, initializer.value_or("")))
+      .getResult();
+}
+
 Value addressOf(OpBuilder builder, Location location, Value operand) {
   auto ctx = builder.getContext();
 
@@ -40,6 +143,60 @@
       .getResult();
 }
 
+Value sizeOf(OpBuilder builder, Location loc, Value value) {
+  auto ctx = builder.getContext();
+  return builder
+      .create<emitc::CallOp>(
+          /*location=*/loc,
+          /*type=*/emitc::OpaqueType::get(ctx, "iree_host_size_t"),
+          /*callee=*/StringAttr::get(ctx, "sizeof"),
+          /*args=*/ArrayAttr{},
+          /*templateArgs=*/ArrayAttr{},
+          /*operands=*/ArrayRef<Value>{value})
+      .getResult(0);
+}
+
+Value sizeOf(OpBuilder builder, Location loc, Attribute attr) {
+  auto ctx = builder.getContext();
+  return builder
+      .create<emitc::CallOp>(
+          /*location=*/loc,
+          /*type=*/emitc::OpaqueType::get(ctx, "iree_host_size_t"),
+          /*callee=*/StringAttr::get(ctx, "sizeof"),
+          /*args=*/ArrayAttr::get(ctx, {attr}),
+          /*templateArgs=*/ArrayAttr{},
+          /*operands=*/ArrayRef<Value>{})
+      .getResult(0);
+}
+
+void memcpy(OpBuilder builder, Location location, Value dest, Value src,
+            Value count) {
+  auto ctx = builder.getContext();
+  builder.create<emitc::CallOp>(
+      /*location=*/location,
+      /*type=*/TypeRange{},
+      /*callee=*/StringAttr::get(ctx, "memcpy"),
+      /*args=*/ArrayAttr{},
+      /*templateArgs=*/ArrayAttr{},
+      /*operands=*/ArrayRef<Value>{dest, src, count});
+}
+
+void memset(OpBuilder builder, Location location, Value dest, int ch,
+            Value count) {
+  auto ctx = builder.getContext();
+  builder.create<emitc::CallOp>(
+      /*location=*/location,
+      /*type=*/TypeRange{},
+      /*callee=*/StringAttr::get(ctx, "memset"),
+      /*args=*/
+      ArrayAttr::get(ctx,
+                     {builder.getIndexAttr(0), builder.getUI32IntegerAttr(ch),
+                      builder.getIndexAttr(1)}),
+      /*templateArgs=*/ArrayAttr{},
+      /*operands=*/
+      ArrayRef<Value>{dest, count});
+}
+
 Value arrayElementAddress(OpBuilder builder, Location location, Type type,
                           IntegerAttr index, Value operand) {
   auto ctx = builder.getContext();
@@ -55,6 +212,22 @@
       .getResult(0);
 }
 
+Value arrayElementAddress(OpBuilder builder, Location location, Type type,
+                          Value index, Value operand) {
+  auto ctx = builder.getContext();
+  return builder
+      .create<emitc::CallOp>(
+          /*location=*/location,
+          /*type=*/type,
+          /*callee=*/StringAttr::get(ctx, "EMITC_ARRAY_ELEMENT_ADDRESS"),
+          /*args=*/
+          ArrayAttr::get(ctx,
+                         {builder.getIndexAttr(0), builder.getIndexAttr(1)}),
+          /*templateArgs=*/ArrayAttr{},
+          /*operands=*/ArrayRef<Value>{operand, index})
+      .getResult(0);
+}
+
 void structDefinition(OpBuilder builder, Location location,
                       StringRef structName, ArrayRef<StructField> fields) {
   std::string structBody;
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/EmitCBuilders.h b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/EmitCBuilders.h
index 4f70baf..c81d327 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/EmitCBuilders.h
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/EmitCBuilders.h
@@ -7,6 +7,8 @@
 #ifndef IREE_COMPILER_DIALECT_VM_CONVERSION_VMTOEMITC_EMITCBUILDERS_H_
 #define IREE_COMPILER_DIALECT_VM_CONVERSION_VMTOEMITC_EMITCBUILDERS_H_
 
+#include <optional>
+
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringRef.h"
 #include "mlir/IR/Builders.h"
@@ -24,13 +26,68 @@
   std::string name;
 };
 
+enum UnaryOperator {
+  // arithmetic
+  PLUS = 0,
+  MINUS,
+  BITWISE_NOT,
+  // logical
+  LOGICAL_NOT,
+};
+
+enum BinaryOperator {
+  // arithmetic
+  ADDITION = 0,
+  SUBTRACTION,
+  PRODUCT,
+  DIVISION,
+  REMAINDER,
+  BITWISE_AND,
+  BITWISE_OR,
+  BITWISE_XOR,
+  BITWISE_LEFT_SHIFT,
+  BITWISE_RIGHT_SHIFT,
+  // logical
+  LOGICAL_AND,
+  LOGICAL_OR,
+  // comparison
+  EQUAL_TO,
+  NOT_EQUAL_TO,
+  LESS_THAN,
+  GREATER_THAN,
+  LESS_THAN_OR_EQUAL,
+  GREATER_THAN_OR_EQUAL,
+};
+
+Value unaryOperator(OpBuilder builder, Location location, UnaryOperator op,
+                    Value operand, Type resultType);
+
+Value binaryOperator(OpBuilder builder, Location location, BinaryOperator op,
+                     Value lhs, Value rhs, Type resultType);
+
+Value allocateVariable(OpBuilder builder, Location location, Type type,
+                       Optional<StringRef> initializer = std::nullopt);
+
 Value addressOf(OpBuilder builder, Location location, Value operand);
 
 Value contentsOf(OpBuilder builder, Location location, Value operand);
 
+Value sizeOf(OpBuilder builder, Location location, Attribute attr);
+
+Value sizeOf(OpBuilder builder, Location location, Value value);
+
+void memcpy(OpBuilder builder, Location location, Value dest, Value src,
+            Value count);
+
+void memset(OpBuilder builder, Location location, Value dest, int ch,
+            Value count);
+
 Value arrayElementAddress(OpBuilder builder, Location location, Type type,
                           IntegerAttr index, Value operand);
 
+Value arrayElementAddress(OpBuilder builder, Location location, Type type,
+                          Value index, Value operand);
+
 void structDefinition(OpBuilder builder, Location location,
                       StringRef structName, ArrayRef<StructField> fields);
 
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
index 6017c55..0449b98 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
@@ -335,7 +335,7 @@
   // CHECK-NEXT: %[[ARGBYTESPANDATA:.+]] = emitc.cast %[[ARGBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[ARGBYTESPAN]], %[[ARGSIZE]]) {args = [0 : index, #emitc.opaque<"data_length">, 1 : index]}
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[ARGBYTESPAN]], %[[ARGBYTESPANDATA]]) {args = [0 : index, #emitc.opaque<"data">, 1 : index]}
-  // CHECK-NEXT: emitc.call "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : i32, 1 : index]}
+  // CHECK-NEXT: emitc.call "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Allocate space for the result.
   // CHECK-NEXT: %[[RESBYTESPAN:.+]] = emitc.call "EMITC_STRUCT_MEMBER_ADDRESS"(%[[ARGSTRUCT]]) {args = [0 : index, #emitc.opaque<"results">]}
@@ -344,7 +344,7 @@
   // CHECK-NEXT: %[[RESBYTESPANDATA:.+]] = emitc.cast %[[RESBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[RESBYTESPAN]], %[[RESULTSIZE]]) {args = [0 : index, #emitc.opaque<"data_length">, 1 : index]}
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[RESBYTESPAN]], %[[RESBYTESPANDATA]]) {args = [0 : index, #emitc.opaque<"data">, 1 : index]}
-  // CHECK-NEXT: emitc.call "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : i32, 1 : index]}
+  // CHECK-NEXT: emitc.call "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Check that we don't pack anything into the argument struct.
   // CHECK-NOT: emitc.call "EMITC_STRUCT_MEMBER"(%[[ARGSTRUCT]]) {args = [0 : index, #emitc.opaque<"arguments">]}
@@ -383,18 +383,18 @@
   // Calculate the size of the arguments.
   // CHECK-NEXT: %[[ARGSIZE0:.+]] = "emitc.constant"() {value = #emitc.opaque<"0">} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[ARGSIZE1:.+]] = emitc.call "sizeof"() {args = [i32]}
-  // CHECK-NEXT: %[[ARGSIZE01:.+]] = emitc.call "EMITC_ADD"(%[[ARGSIZE0]], %[[ARGSIZE1]])
+  // CHECK-NEXT: %[[ARGSIZE01:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSIZE0]], %[[ARGSIZE1]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
   // CHECK-NEXT: %[[ARGSIZE2:.+]] = emitc.call "sizeof"() {args = [i32]}
-  // CHECK-NEXT: %[[ARGSIZE012:.+]] = emitc.call "EMITC_ADD"(%[[ARGSIZE01]], %[[ARGSIZE2]])
+  // CHECK-NEXT: %[[ARGSIZE012:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSIZE01]], %[[ARGSIZE2]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
   // CHECK-NEXT: %[[ARGSIZE3:.+]] = emitc.call "sizeof"() {args = [i32]}
-  // CHECK-NEXT: %[[ARGSIZE0123:.+]] = emitc.call "EMITC_ADD"(%[[ARGSIZE012]], %[[ARGSIZE3]])
+  // CHECK-NEXT: %[[ARGSIZE0123:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSIZE012]], %[[ARGSIZE3]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
   // CHECK-NEXT: %[[ARGSIZE4:.+]] = emitc.call "sizeof"() {args = [i32]}
-  // CHECK-NEXT: %[[ARGSIZE:.+]] = emitc.call "EMITC_ADD"(%[[ARGSIZE0123]], %[[ARGSIZE4]])
+  // CHECK-NEXT: %[[ARGSIZE:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSIZE0123]], %[[ARGSIZE4]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
 
   // Calculate the size of the result.
   // CHECK-NEXT: %[[RESULTSIZE0:.+]] = "emitc.constant"() {value = #emitc.opaque<"0">} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[RESULTSIZE1:.+]] = emitc.call "sizeof"() {args = [i32]}
-  // CHECK-NEXT: %[[RESULTSIZE:.+]] = emitc.call "EMITC_ADD"(%[[RESULTSIZE0]], %[[RESULTSIZE1]])
+  // CHECK-NEXT: %[[RESULTSIZE:.+]] = emitc.call "EMITC_BINARY"(%[[RESULTSIZE0]], %[[RESULTSIZE1]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
 
   // Create a struct for the arguments and results.
   // CHECK: %[[ARGSTRUCT:.+]] = "emitc.constant"() {value = #emitc.opaque<"">} : () -> !emitc.opaque<"iree_vm_function_call_t">
@@ -409,7 +409,7 @@
   // CHECK-NEXT: %[[ARGBYTESPANDATA:.+]] = emitc.cast %[[ARGBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[ARGBYTESPAN]], %[[ARGSIZE]]) {args = [0 : index, #emitc.opaque<"data_length">, 1 : index]}
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[ARGBYTESPAN]], %[[ARGBYTESPANDATA]]) {args = [0 : index, #emitc.opaque<"data">, 1 : index]}
-  // CHECK-NEXT: emitc.call "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : i32, 1 : index]}
+  // CHECK-NEXT: emitc.call "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Allocate space for the result.
   // CHECK-NEXT: %[[RESBYTESPAN:.+]] = emitc.call "EMITC_STRUCT_MEMBER_ADDRESS"(%[[ARGSTRUCT]]) {args = [0 : index, #emitc.opaque<"results">]}
@@ -419,7 +419,7 @@
   // CHECK-NEXT: %[[RESBYTESPANDATA:.+]] = emitc.cast %[[RESBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[RESBYTESPAN]], %[[RESULTSIZE]]) {args = [0 : index, #emitc.opaque<"data_length">, 1 : index]}
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[RESBYTESPAN]], %[[RESBYTESPANDATA]]) {args = [0 : index, #emitc.opaque<"data">, 1 : index]}
-  // CHECK-NEXT: emitc.call "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : i32, 1 : index]}
+  // CHECK-NEXT: emitc.call "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Pack the arguments into the struct.
   // Here we also create pointers for non-pointer types.
@@ -431,19 +431,19 @@
   // CHECK-NEXT: %[[A1PTR:.+]] = emitc.apply "&"(%arg2) : (i32) -> !emitc.ptr<i32>
   // CHECK-NEXT: emitc.call "memcpy"(%[[ARGSPTR]], %[[A1PTR]], %[[ARGHOSTSIZE]])
   // CHECK-NEXT: %[[ARGHOSTSIZE2:.+]] = emitc.call "sizeof"() {args = [i32]} : () -> !emitc.opaque<"iree_host_size_t">
-  // CHECK-NEXT: %[[A1ADDR:.+]] = emitc.call "EMITC_ADD"(%[[ARGSPTR]], %[[ARGHOSTSIZE2]])
+  // CHECK-NEXT: %[[A1ADDR:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSPTR]], %[[ARGHOSTSIZE2]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
   // CHECK-SAME:     : (!emitc.ptr<ui8>, !emitc.opaque<"iree_host_size_t">) -> !emitc.ptr<ui8>
   // CHECK-NEXT: %[[A1SIZE:.+]] = emitc.call "sizeof"() {args = [i32]} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[A2PTR:.+]] = emitc.apply "&"(%arg3) : (i32) -> !emitc.ptr<i32>
   // CHECK-NEXT: emitc.call "memcpy"(%[[A1ADDR]], %[[A2PTR]], %[[A1SIZE]])
   // CHECK-NEXT: %[[A1SIZE2:.+]] = emitc.call "sizeof"() {args = [i32]} : () -> !emitc.opaque<"iree_host_size_t">
-  // CHECK-NEXT: %[[A2ADDR:.+]] = emitc.call "EMITC_ADD"(%[[A1ADDR]], %[[A1SIZE2]])
+  // CHECK-NEXT: %[[A2ADDR:.+]] = emitc.call "EMITC_BINARY"(%[[A1ADDR]], %[[A1SIZE2]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
   // CHECK-SAME:     : (!emitc.ptr<ui8>, !emitc.opaque<"iree_host_size_t">) -> !emitc.ptr<ui8>
   // CHECK-NEXT: %[[A2SIZE:.+]] = emitc.call "sizeof"() {args = [i32]} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[A3PTR:.+]] = emitc.apply "&"(%arg4) : (i32) -> !emitc.ptr<i32>
   // CHECK-NEXT: emitc.call "memcpy"(%[[A2ADDR]], %[[A3PTR]], %[[A2SIZE]])
   // CHECK-NEXT: %[[A2SIZE2:.+]] = emitc.call "sizeof"() {args = [i32]} : () -> !emitc.opaque<"iree_host_size_t">
-  // CHECK-NEXT: %[[A3ADDR:.+]] = emitc.call "EMITC_ADD"(%[[A2ADDR]], %[[A2SIZE2]])
+  // CHECK-NEXT: %[[A3ADDR:.+]] = emitc.call "EMITC_BINARY"(%[[A2ADDR]], %[[A2SIZE2]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
   // CHECK-SAME:     : (!emitc.ptr<ui8>, !emitc.opaque<"iree_host_size_t">) -> !emitc.ptr<ui8>
   // CHECK-NEXT: %[[A3SIZE:.+]] = emitc.call "sizeof"() {args = [i32]} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[A4PTR:.+]] = emitc.apply "&"(%arg5) : (i32) -> !emitc.ptr<i32>
@@ -486,14 +486,14 @@
   // Calculate the size of the arguments.
   // CHECK-NEXT: %[[ARGSIZE0:.+]] = "emitc.constant"() {value = #emitc.opaque<"0">} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[ARGSIZE1:.+]] = emitc.call "sizeof"() {args = [i32]}
-  // CHECK-NEXT: %[[ARGSIZE01:.+]] = emitc.call "EMITC_ADD"(%[[ARGSIZE0]], %[[ARGSIZE1]])
+  // CHECK-NEXT: %[[ARGSIZE01:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSIZE0]], %[[ARGSIZE1]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
   // CHECK-NEXT: %[[ARGSIZE2:.+]] = emitc.call "sizeof"() {args = [i32]}
-  // CHECK-NEXT: %[[ARGSIZE:.+]] = emitc.call "EMITC_ADD"(%[[ARGSIZE01]], %[[ARGSIZE2]])
+  // CHECK-NEXT: %[[ARGSIZE:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSIZE01]], %[[ARGSIZE2]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
 
   // Calculate the size of the result.
   // CHECK-NEXT: %[[RESULTSIZE0:.+]] = "emitc.constant"() {value = #emitc.opaque<"0">} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[RESULTSIZE1:.+]] = emitc.call "sizeof"() {args = [i32]}
-  // CHECK-NEXT: %[[RESULTSIZE:.+]] = emitc.call "EMITC_ADD"(%[[RESULTSIZE0]], %[[RESULTSIZE1]])
+  // CHECK-NEXT: %[[RESULTSIZE:.+]] = emitc.call "EMITC_BINARY"(%[[RESULTSIZE0]], %[[RESULTSIZE1]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
 
   // Create a struct for the arguments and results.
   // CHECK: %[[ARGSTRUCT:.+]] = "emitc.constant"() {value = #emitc.opaque<"">} : () -> !emitc.opaque<"iree_vm_function_call_t">
@@ -508,7 +508,7 @@
   // CHECK-NEXT: %[[ARGBYTESPANDATA:.+]] = emitc.cast %[[ARGBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[ARGBYTESPAN]], %[[ARGSIZE]]) {args = [0 : index, #emitc.opaque<"data_length">, 1 : index]}
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[ARGBYTESPAN]], %[[ARGBYTESPANDATA]]) {args = [0 : index, #emitc.opaque<"data">, 1 : index]}
-  // CHECK-NEXT: emitc.call "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : i32, 1 : index]}
+  // CHECK-NEXT: emitc.call "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Allocate space for the result.
   // CHECK-NEXT: %[[RESBYTESPAN:.+]] = emitc.call "EMITC_STRUCT_MEMBER_ADDRESS"(%[[ARGSTRUCT]]) {args = [0 : index, #emitc.opaque<"results">]}
@@ -518,7 +518,7 @@
   // CHECK-NEXT: %[[RESBYTESPANDATA:.+]] = emitc.cast %[[RESBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[RESBYTESPAN]], %[[RESULTSIZE]]) {args = [0 : index, #emitc.opaque<"data_length">, 1 : index]}
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[RESBYTESPAN]], %[[RESBYTESPANDATA]]) {args = [0 : index, #emitc.opaque<"data">, 1 : index]}
-  // CHECK-NEXT: emitc.call "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : i32, 1 : index]}
+  // CHECK-NEXT: emitc.call "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Pack the arguments into the struct.
   // Here we also create pointers for non-pointer types.
@@ -530,7 +530,7 @@
   // CHECK-NEXT: %[[A1PTR:.+]] = emitc.apply "&"(%arg2) : (i32) -> !emitc.ptr<i32>
   // CHECK-NEXT: emitc.call "memcpy"(%[[ARGSPTR]], %[[A1PTR]], %[[ARGHOSTSIZE]])
   // CHECK-NEXT: %[[ARGHOSTSIZE2:.+]] = emitc.call "sizeof"() {args = [i32]} : () -> !emitc.opaque<"iree_host_size_t">
-  // CHECK-NEXT: %[[A1ADDR:.+]] = emitc.call "EMITC_ADD"(%[[ARGSPTR]], %[[ARGHOSTSIZE2]])
+  // CHECK-NEXT: %[[A1ADDR:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSPTR]], %[[ARGHOSTSIZE2]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
   // CHECK-SAME:     : (!emitc.ptr<ui8>, !emitc.opaque<"iree_host_size_t">) -> !emitc.ptr<ui8>
   // CHECK-NEXT: %[[A1SIZE:.+]] = emitc.call "sizeof"() {args = [i32]} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[A2PTR:.+]] = emitc.apply "&"(%arg3) : (i32) -> !emitc.ptr<i32>
@@ -573,12 +573,12 @@
   // Calculate the size of the arguments.
   // CHECK-NEXT: %[[ARGSIZE0:.+]] = "emitc.constant"() {value = #emitc.opaque<"0">} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[ARGSIZE1:.+]] = emitc.call "sizeof"() {args = [!emitc.opaque<"iree_vm_ref_t">]}
-  // CHECK-NEXT: %[[ARGSIZE:.+]] = emitc.call "EMITC_ADD"(%[[ARGSIZE0]], %[[ARGSIZE1]])
+  // CHECK-NEXT: %[[ARGSIZE:.+]] = emitc.call "EMITC_BINARY"(%[[ARGSIZE0]], %[[ARGSIZE1]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
 
   // Calculate the size of the result.
   // CHECK-NEXT: %[[RESULTSIZE0:.+]] = "emitc.constant"() {value = #emitc.opaque<"0">} : () -> !emitc.opaque<"iree_host_size_t">
   // CHECK-NEXT: %[[RESULTSIZE1:.+]] = emitc.call "sizeof"() {args = [!emitc.opaque<"iree_vm_ref_t">]}
-  // CHECK-NEXT: %[[RESULTSIZE:.+]] = emitc.call "EMITC_ADD"(%[[RESULTSIZE0]], %[[RESULTSIZE1]])
+  // CHECK-NEXT: %[[RESULTSIZE:.+]] = emitc.call "EMITC_BINARY"(%[[RESULTSIZE0]], %[[RESULTSIZE1]]) {args = [#emitc.opaque<"+">, 0 : index, 1 : index]}
 
   // Create a struct for the arguments and results.
   // CHECK: %[[ARGSTRUCT:.+]] = "emitc.constant"() {value = #emitc.opaque<"">} : () -> !emitc.opaque<"iree_vm_function_call_t">
@@ -592,7 +592,7 @@
   // CHECK-NEXT: %[[ARGBYTESPANDATA:.+]] = emitc.cast %[[ARGBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[ARGBYTESPAN]], %[[ARGSIZE]]) {args = [0 : index, #emitc.opaque<"data_length">, 1 : index]}
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[ARGBYTESPAN]], %[[ARGBYTESPANDATA]]) {args = [0 : index, #emitc.opaque<"data">, 1 : index]}
-  // CHECK-NEXT: emitc.call "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : i32, 1 : index]}
+  // CHECK-NEXT: emitc.call "memset"(%[[ARGBYTESPANDATA]], %[[ARGSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Allocate space for the result.
   // CHECK-NEXT: %[[RESBYTESPAN:.+]] = emitc.call "EMITC_STRUCT_MEMBER_ADDRESS"(%[[ARGSTRUCT]]) {args = [0 : index, #emitc.opaque<"results">]}
@@ -601,7 +601,7 @@
   // CHECK-NEXT: %[[RESBYTESPANDATA:.+]] = emitc.cast %[[RESBYTESPANDATAVOID]] : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<ui8>
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[RESBYTESPAN]], %[[RESULTSIZE]]) {args = [0 : index, #emitc.opaque<"data_length">, 1 : index]}
   // CHECK-NEXT: emitc.call "EMITC_STRUCT_PTR_MEMBER_ASSIGN"(%[[RESBYTESPAN]], %[[RESBYTESPANDATA]]) {args = [0 : index, #emitc.opaque<"data">, 1 : index]}
-  // CHECK-NEXT: emitc.call "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : i32, 1 : index]}
+  // CHECK-NEXT: emitc.call "memset"(%[[RESBYTESPANDATA]], %[[RESULTSIZE]]) {args = [0 : index, 0 : ui32, 1 : index]}
 
   // Pack the argument into the struct.
   // CHECK-NEXT: %[[ARGS:.+]] = emitc.call "EMITC_STRUCT_MEMBER"(%[[ARGSTRUCT]]) {args = [0 : index, #emitc.opaque<"arguments">]}
@@ -721,8 +721,8 @@
     // CHECK-NEXT: %[[IMPORTS:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%arg2) {args = [0 : index, #emitc.opaque<"imports">]} : (!emitc.ptr<!emitc.opaque<"my_module_state_t">>) -> !emitc.ptr<!emitc.opaque<"iree_vm_function_t">>
     // CHECK-NEXT: %[[IMPORT:.+]] = emitc.call "EMITC_ARRAY_ELEMENT_ADDRESS"(%[[IMPORTS]]) {args = [0 : index, 0 : ui32]} : (!emitc.ptr<!emitc.opaque<"iree_vm_function_t">>) -> !emitc.ptr<!emitc.opaque<"iree_vm_function_t">>
     // CHECK-NEXT: %[[MODULE:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%[[IMPORT]]) {args = [0 : index, #emitc.opaque<"module">]} : (!emitc.ptr<!emitc.opaque<"iree_vm_function_t">>) -> !emitc.ptr<!emitc.opaque<"iree_vm_module_t">>
-    // CHECK-NEXT: %[[CONDITION0:.+]] = emitc.call "EMITC_NOT"(%[[MODULE]]) {args = [0 : index]} : (!emitc.ptr<!emitc.opaque<"iree_vm_module_t">>) -> i1
-    // CHECK-NEXT: %[[CONDITION1:.+]] = emitc.call "EMITC_NOT"(%[[CONDITION0]]) {args = [0 : index]} : (i1) -> i1
+    // CHECK-NEXT: %[[CONDITION0:.+]] = emitc.call "EMITC_UNARY"(%[[MODULE]]) {args = [#emitc.opaque<"!">, 0 : index]} : (!emitc.ptr<!emitc.opaque<"iree_vm_module_t">>) -> i1
+    // CHECK-NEXT: %[[CONDITION1:.+]] = emitc.call "EMITC_UNARY"(%[[CONDITION0]]) {args = [#emitc.opaque<"!">, 0 : index]} : (i1) -> i1
     // CHECK-NEXT: %[[RESULT:.+]] = emitc.cast %[[CONDITION1]] : i1 to i32
     %has_optional_import_fn = vm.import.resolved @optional_import_fn : i32
     vm.return %has_optional_import_fn : i32
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/global_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/global_ops.mlir
index ec1eb7a..6587e82 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/global_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/global_ops.mlir
@@ -36,8 +36,8 @@
     // CHECK: %[[A:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%arg2) {args = [0 : index, #emitc.opaque<"refs">]} : (!emitc.ptr<!emitc.opaque<"my_module_state_t">>) -> !emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>
     // CHECK: %[[B:.+]] = emitc.call "EMITC_ARRAY_ELEMENT_ADDRESS"(%[[A]]) {args = [0 : index, 0 : ui32]} : (!emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>) -> !emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>
     // CHECK: %[[C:.+]] = "emitc.variable"() {value = #emitc.opaque<"">} : () -> !emitc.opaque<"iree_vm_type_def_t">
-    // CHECK: emitc.call "EMITC_STRUCT_MEMBER_ASSIGN"{{.*}}
     // CHECK: %[[D:.+]] = emitc.apply "&"(%[[C]]) : (!emitc.opaque<"iree_vm_type_def_t">) -> !emitc.ptr<!emitc.opaque<"iree_vm_type_def_t">>
+    // CHECK: emitc.call "EMITC_STRUCT_MEMBER_ASSIGN"{{.*}}
     // CHECK: %[[E:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%[[D]]) {args = [0 : index, #emitc.opaque<"ref_type">]} : (!emitc.ptr<!emitc.opaque<"iree_vm_type_def_t">>) -> !emitc.opaque<"iree_vm_ref_type_t">
     // CHECK: %{{.+}} = emitc.call "iree_vm_ref_retain_or_move_checked"(%[[B]], %[[E]], %arg3) {args = [false, 0 : index, 1 : index, 2 : index]} : (!emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>, !emitc.opaque<"iree_vm_ref_type_t">, !emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>) -> !emitc.opaque<"iree_status_t">
     %0 = vm.global.load.ref @g0 : !vm.buffer
@@ -55,8 +55,8 @@
     // CHECK: %[[A:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%arg2) {args = [0 : index, #emitc.opaque<"refs">]} : (!emitc.ptr<!emitc.opaque<"my_module_state_t">>) -> !emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>
     // CHECK: %[[B:.+]] = emitc.call "EMITC_ARRAY_ELEMENT_ADDRESS"(%[[A]]) {args = [0 : index, 0 : ui32]} : (!emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>) -> !emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>
     // CHECK: %[[C:.+]] = "emitc.variable"() {value = #emitc.opaque<"">} : () -> !emitc.opaque<"iree_vm_type_def_t">
-    // CHECK: emitc.call "EMITC_STRUCT_MEMBER_ASSIGN"{{.*}}
     // CHECK: %[[D:.+]] = emitc.apply "&"(%[[C]]) : (!emitc.opaque<"iree_vm_type_def_t">) -> !emitc.ptr<!emitc.opaque<"iree_vm_type_def_t">>
+    // CHECK: emitc.call "EMITC_STRUCT_MEMBER_ASSIGN"{{.*}}
     // CHECK: %[[E:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%[[D]]) {args = [0 : index, #emitc.opaque<"ref_type">]} : (!emitc.ptr<!emitc.opaque<"iree_vm_type_def_t">>) -> !emitc.opaque<"iree_vm_ref_type_t">
     // CHECK: %{{.+}} = emitc.call "iree_vm_ref_retain_or_move_checked"(%arg3, %[[E]], %[[B]]) {args = [false, 0 : index, 1 : index, 2 : index]} : (!emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>, !emitc.opaque<"iree_vm_ref_type_t">, !emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>) -> !emitc.opaque<"iree_status_t">
     vm.global.store.ref %arg0, @g0_mut : !vm.buffer
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/list_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/list_ops.mlir
index 1776351..fc451e7 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/list_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/list_ops.mlir
@@ -3,8 +3,8 @@
 vm.module @my_module {
   // CHECK-LABEL: @my_module_list_alloc
   vm.func @list_alloc(%arg0: i32) -> !vm.list<i32> {
-    // CHECK: emitc.call "EMITC_STRUCT_MEMBER_ASSIGN"
     // CHECK: %[[A:.+]] = emitc.apply "&"(%{{.+}}) : (!emitc.opaque<"iree_vm_type_def_t">) -> !emitc.ptr<!emitc.opaque<"iree_vm_type_def_t">>
+    // CHECK: emitc.call "EMITC_STRUCT_MEMBER_ASSIGN"
     // CHECK: %[[B:.+]] = "emitc.variable"() {value = #emitc.opaque<"NULL">} : () -> !emitc.ptr<!emitc.opaque<"iree_vm_list_t">>
     // CHECK: %[[C:.+]] = emitc.apply "&"(%[[B]]) : (!emitc.ptr<!emitc.opaque<"iree_vm_list_t">>) -> !emitc.ptr<!emitc.ptr<!emitc.opaque<"iree_vm_list_t">>>
     // CHECK: %[[D:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%arg2) {args = [0 : index, #emitc.opaque<"allocator">]} : (!emitc.ptr<!emitc.opaque<"my_module_state_t">>) -> !emitc.opaque<"iree_allocator_t">
@@ -82,10 +82,10 @@
     // CHECK: %[[B:.+]] = "emitc.constant"() {value = #emitc.opaque<"IREE_VM_REF_TYPE_NULL">} : () -> !emitc.opaque<"iree_vm_ref_type_t">
     // CHECK: %[[C:.+]] = emitc.call "iree_vm_type_def_is_value"(%{{.+}}) : (!emitc.ptr<!emitc.opaque<"iree_vm_type_def_t">>) -> i1
     // CHECK: %[[D:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%{{.+}}) {args = [0 : index, #emitc.opaque<"ref_type">]} : (!emitc.ptr<!emitc.opaque<"iree_vm_type_def_t">>) -> !emitc.opaque<"iree_vm_ref_type_t">
-    // CHECK: %[[E:.+]] = emitc.call "EMITC_NE"(%[[A]], %[[B]]) : (!emitc.opaque<"iree_vm_ref_type_t">, !emitc.opaque<"iree_vm_ref_type_t">) -> i1
-    // CHECK: %[[F:.+]] = emitc.call "EMITC_NE"(%[[A]], %[[D]]) : (!emitc.opaque<"iree_vm_ref_type_t">, !emitc.opaque<"iree_vm_ref_type_t">) -> i1
-    // CHECK: %[[G:.+]] = emitc.call "EMITC_OR"(%[[C]], %[[F]]) : (i1, i1) -> i1
-    // CHECK: %{{.+}} = emitc.call "EMITC_AND"(%[[E]], %[[G]]) : (i1, i1) -> i1
+    // CHECK: %[[E:.+]] = emitc.call "EMITC_BINARY"(%[[A]], %[[B]]) {args = [#emitc.opaque<"!=">, 0 : index, 1 : index]} : (!emitc.opaque<"iree_vm_ref_type_t">, !emitc.opaque<"iree_vm_ref_type_t">) -> i1
+    // CHECK: %[[F:.+]] = emitc.call "EMITC_BINARY"(%[[A]], %[[D]]) {args = [#emitc.opaque<"!=">, 0 : index, 1 : index]} : (!emitc.opaque<"iree_vm_ref_type_t">, !emitc.opaque<"iree_vm_ref_type_t">) -> i1
+    // CHECK: %[[G:.+]] = emitc.call "EMITC_BINARY"(%[[C]], %[[F]]) {args = [#emitc.opaque<"||">, 0 : index, 1 : index]} : (i1, i1) -> i1
+    // CHECK: %{{.+}} = emitc.call "EMITC_BINARY"(%[[E]], %[[G]]) {args = [#emitc.opaque<"&&">, 0 : index, 1 : index]} : (i1, i1) -> i1
     // CHECK: cf.cond_br %{{.+}}, ^[[FAIL:.+]], ^[[CONTINUE:.+]]
     // CHECK: ^[[FAIL]]:
     // CHECK-NEXT: emitc.call "iree_vm_ref_release"(%arg3) : (!emitc.ptr<!emitc.opaque<"iree_vm_ref_t">>) -> ()
diff --git a/runtime/src/iree/vm/ops_emitc.h b/runtime/src/iree/vm/ops_emitc.h
index 2375160..60b4062 100644
--- a/runtime/src/iree/vm/ops_emitc.h
+++ b/runtime/src/iree/vm/ops_emitc.h
@@ -49,15 +49,10 @@
 // Get the address of an array element
 #define EMITC_ARRAY_ELEMENT_ADDRESS(array, index) &(array)[index]
 
-// Unary operations
-#define EMITC_NOT(arg) (!(arg))
+// Unary operators
+#define EMITC_UNARY(op, arg) (op(arg))
 
-// Binary operations
-#define EMITC_AND(lhs, rhs) ((lhs) && (rhs))
-#define EMITC_EQ(lhs, rhs) ((lhs) == (rhs))
-#define EMITC_NE(lhs, rhs) ((lhs) != (rhs))
-#define EMITC_OR(lhs, rhs) ((lhs) || (rhs))
-
-#define EMITC_ADD(lhs, rhs) ((lhs) + (rhs))
+// Binary operators
+#define EMITC_BINARY(op, lhs, rhs) ((lhs)op(rhs))
 
 #endif  // IREE_VM_OPS_EMITC_H_