Add a VM to EmitC conversion and CModule Target (#2536)

Makes first progress on #1173:
* Defines `IREE_HAVE_EMITC_DIALECT` to enable conditional builds, which
  are CMake only so far
* Enables the EmitC dialect in iree-opt and iree-translate
* Adds a VMToEmitC conversion (test) pass
* Adds a C target to the VM

So far an empty file is written. In a follow up, we will implement
EmitC -> textual C, (re)using EmitC's CppTarget within the CModule target.

Co-authored-by: Simon Camphausen <simon.camphausen@iml.fraunhofer.de>
diff --git a/SUBMODULE_VERSIONS b/SUBMODULE_VERSIONS
index d57f928..40f4165 100644
--- a/SUBMODULE_VERSIONS
+++ b/SUBMODULE_VERSIONS
@@ -6,7 +6,7 @@
 f2fb48c3b3d79a75a88a99fba6576b25d42ec528 third_party/googletest
 754deffd11c733d709c3ed66d3b9a6b54d081474 third_party/llvm-project
 17b12a4481daa150e2d1ea3ada086b551b856707 third_party/marl
-67f3ccebee84f3488b46a8d3ac005178c52ff264 third_party/mlir-emitc
+80885f899e12d55a45561ef758eea47bb340dbf1 third_party/mlir-emitc
 d8c7ee00a687ac369e62e2032514a93a9b413502 third_party/pybind11
 9f53ba413e6fc879236dcaa3e008915973d67a4f third_party/ruy
 a1390ed39ec77ecfb574bc6fcd5bfc5e3adbdea9 third_party/sdl2
diff --git a/build_tools/cmake/iree_copts.cmake b/build_tools/cmake/iree_copts.cmake
index c39db21..7563c1f 100644
--- a/build_tools/cmake/iree_copts.cmake
+++ b/build_tools/cmake/iree_copts.cmake
@@ -250,3 +250,15 @@
   ${PROJECT_BINARY_DIR}/build_tools/third_party/tensorflow
   ${PROJECT_BINARY_DIR}/build_tools/third_party/tensorflow/tensorflow/compiler/mlir/hlo/include/
 )
+
+#-------------------------------------------------------------------------------
+# Third party: mlir-emitc
+#-------------------------------------------------------------------------------
+
+if(IREE_ENABLE_EMITC)
+  list(APPEND IREE_COMMON_INCLUDE_DIRS
+    ${PROJECT_SOURCE_DIR}/third_party/mlir-emitc/include
+    ${PROJECT_BINARY_DIR}/third_party/mlir-emitc/include
+  )
+  add_definitions(-DIREE_HAVE_EMITC_DIALECT)
+endif()
diff --git a/iree/compiler/Dialect/VM/Conversion/VMToEmitC/CMakeLists.txt b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/CMakeLists.txt
new file mode 100644
index 0000000..f4f29c9
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/CMakeLists.txt
@@ -0,0 +1,33 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+if(${IREE_ENABLE_EMITC})
+  iree_add_all_subdirs()
+  
+  iree_cc_library(
+    NAME
+      VMToEmitC
+    HDRS
+      "ConvertVMToEmitC.h"
+    SRCS
+      "ConvertVMToEmitC.cpp"
+    DEPS
+      MLIRIR
+      MLIRPass
+      MLIREmitC
+      MLIRTransforms
+      iree::compiler::Dialect::VM::IR
+    PUBLIC
+  )
+endif()
diff --git a/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp
new file mode 100644
index 0000000..1d6d96e
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.cpp
@@ -0,0 +1,108 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.h"
+
+#include "emitc/Dialect/EmitC/EmitCDialect.h"
+#include "iree/compiler/Dialect/VM/IR/VMOps.h"
+#include "mlir/IR/Matchers.h"
+#include "mlir/IR/Module.h"
+#include "mlir/Transforms/DialectConversion.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+namespace {
+
+// Taken over from StandardToVM.
+// We need to replace the Op depending on the operand.
+// We could start with a conversion for IREE::VM::AddI32Op
+template <typename SrcOpTy, typename DstOpTy>
+class BinaryArithmeticOpConversion : public OpConversionPattern<SrcOpTy> {
+  using OpConversionPattern<SrcOpTy>::OpConversionPattern;
+
+ public:
+  BinaryArithmeticOpConversion(MLIRContext *context, StringRef funcName)
+      : OpConversionPattern<SrcOpTy>(context), funcName(funcName) {}
+
+ private:
+  LogicalResult matchAndRewrite(
+      SrcOpTy srcOp, ArrayRef<Value> operands,
+      ConversionPatternRewriter &rewriter) const override {
+    typename SrcOpTy::Adaptor srcAdapter(operands);
+
+    StringAttr callee = rewriter.getStringAttr(funcName);
+    ArrayAttr args =
+        rewriter.getArrayAttr({IntegerAttr::get(rewriter.getIndexType(), 0),
+                               IntegerAttr::get(rewriter.getIndexType(), 1)});
+    ValueRange dstOperands{srcAdapter.lhs(), srcAdapter.rhs()};
+
+    rewriter.replaceOpWithNewOp<DstOpTy>(srcOp, srcAdapter.lhs().getType(),
+                                         callee, args, dstOperands);
+
+    return success();
+  }
+
+  StringRef funcName;
+};
+
+}  // namespace
+
+void populateVMToCPatterns(MLIRContext *context,
+                           OwningRewritePatternList &patterns) {
+  patterns.insert<
+      BinaryArithmeticOpConversion<IREE::VM::AddI32Op, mlir::emitc::CallOp>>(
+      context, "vm_add_i32");
+}
+
+namespace IREE {
+namespace VM {
+
+namespace {
+
+// A pass converting IREE VM operations into the EmitC dialect.
+class ConvertVMToEmitCPass
+    : public PassWrapper<ConvertVMToEmitCPass,
+                         OperationPass<IREE::VM::ModuleOp>> {
+  void runOnOperation() {
+    ConversionTarget target(getContext());
+
+    OwningRewritePatternList patterns;
+    populateVMToCPatterns(&getContext(), patterns);
+
+    target.addLegalDialect<mlir::emitc::EmitCDialect>();
+    target.addLegalDialect<IREE::VM::VMDialect>();
+    target.addIllegalOp<IREE::VM::AddI32Op>();
+
+    if (failed(applyFullConversion(getOperation(), target, patterns))) {
+      return signalPassFailure();
+    }
+  }
+};
+
+}  // namespace
+
+std::unique_ptr<OperationPass<IREE::VM::ModuleOp>>
+createConvertVMToEmitCPass() {
+  return std::make_unique<ConvertVMToEmitCPass>();
+}
+
+}  // namespace VM
+}  // namespace IREE
+
+static PassRegistration<IREE::VM::ConvertVMToEmitCPass> pass(
+    "iree-convert-vm-to-emitc", "Convert VM Ops to the EmitC dialect");
+
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.h b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.h
new file mode 100644
index 0000000..a1bc545
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.h
@@ -0,0 +1,38 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_COMPILER_DIALECT_VM_CONVERSION_VMTOEMITC_CONVERTVMTOEMITC_H_
+#define IREE_COMPILER_DIALECT_VM_CONVERSION_VMTOEMITC_CONVERTVMTOEMITC_H_
+
+#include "iree/compiler/Dialect/VM/IR/VMOps.h"
+#include "mlir/Pass/Pass.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+void populateVMToCPatterns(MLIRContext *context,
+                           OwningRewritePatternList &patterns);
+
+namespace IREE {
+namespace VM {
+
+std::unique_ptr<OperationPass<IREE::VM::ModuleOp>> createConvertVMToEmitCPass();
+
+}  // namespace VM
+}  // namespace IREE
+
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_VM_CONVERSION_VMTOEMITC_CONVERTVMTOEMITC_H_
diff --git a/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/CMakeLists.txt b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/CMakeLists.txt
new file mode 100644
index 0000000..fcc538b
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+iree_add_all_subdirs()
+
+file(GLOB _GLOB_X_MLIR LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS *.mlir)
+iree_lit_test_suite(
+  NAME
+    lit
+  SRCS
+    "${_GLOB_X_MLIR}"
+  DATA
+    iree::tools::IreeFileCheck
+    iree::tools::iree-opt
+)
diff --git a/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/add.mlir b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/add.mlir
new file mode 100644
index 0000000..a693fe5
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/add.mlir
@@ -0,0 +1,12 @@
+// RUN: iree-opt -split-input-file -pass-pipeline='iree-convert-vm-to-emitc' %s | IreeFileCheck %s
+
+// CHECK: vm.module @add_module {
+vm.module @add_module {
+  // CHECK-NEXT: vm.func @add_i32(%arg0: i32, %arg1: i32) {
+  vm.func @add_i32(%arg0: i32, %arg1: i32) {
+    // CHECK-NEXT: %0 = emitc.call "vm_add_i32"(%arg0, %arg1) {args = [0 : index, 1 : index]} : (i32, i32) -> i32
+    %0 = vm.add.i32 %arg0, %arg1 : i32
+    // CHECK-NEXT: vm.return
+    vm.return
+  }
+}
diff --git a/iree/compiler/Dialect/VM/Target/C/CMakeLists.txt b/iree/compiler/Dialect/VM/Target/C/CMakeLists.txt
new file mode 100644
index 0000000..5183093
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Target/C/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+if(${IREE_ENABLE_EMITC})
+  iree_add_all_subdirs()
+  
+  iree_cc_library(
+    NAME
+      C
+    HDRS
+      "CModuleTarget.h"
+    SRCS
+      "CModuleTarget.cpp"
+      "TranslationRegistration.cpp"
+    DEPS
+      LLVMSupport
+      MLIRIR
+      MLIRPass
+      MLIRSupport
+      iree::compiler::Dialect::VM::IR
+      iree::compiler::Dialect::VM::Conversion::VMToEmitC
+    PUBLIC
+  )
+endif()
diff --git a/iree/compiler/Dialect/VM/Target/C/CModuleTarget.cpp b/iree/compiler/Dialect/VM/Target/C/CModuleTarget.cpp
new file mode 100644
index 0000000..923be34
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Target/C/CModuleTarget.cpp
@@ -0,0 +1,54 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "iree/compiler/Dialect/VM/Target/C/CModuleTarget.h"
+
+#include "iree/compiler/Dialect/VM/Conversion/VMToEmitC/ConvertVMToEmitC.h"
+#include "mlir/Pass/PassManager.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace VM {
+
+LogicalResult translateModuleToC(IREE::VM::ModuleOp moduleOp,
+                                 llvm::raw_ostream &output) {
+  // TODO: implement translation
+  output << "// c module stub\n";
+
+  return success();
+}
+
+LogicalResult translateModuleToC(mlir::ModuleOp outerModuleOp,
+                                 llvm::raw_ostream &output) {
+  PassManager pm(outerModuleOp.getContext());
+
+  pm.addPass(createConvertVMToEmitCPass());
+
+  if (failed(pm.run(outerModuleOp))) {
+    return failure();
+  }
+
+  auto moduleOps = outerModuleOp.getOps<IREE::VM::ModuleOp>();
+  if (moduleOps.empty()) {
+    return outerModuleOp.emitError()
+           << "outer module does not contain a vm.module op";
+  }
+  return translateModuleToC(*moduleOps.begin(), output);
+}
+
+}  // namespace VM
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/iree/compiler/Dialect/VM/Target/C/CModuleTarget.h b/iree/compiler/Dialect/VM/Target/C/CModuleTarget.h
new file mode 100644
index 0000000..1268b3a
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Target/C/CModuleTarget.h
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_COMPILER_DIALECT_VM_TARGET_C_CMODULETARGET_H_
+#define IREE_COMPILER_DIALECT_VM_TARGET_C_CMODULETARGET_H_
+
+#include "iree/compiler/Dialect/VM/IR/VMOps.h"
+#include "llvm/Support/raw_ostream.h"
+#include "mlir/IR/Module.h"
+#include "mlir/Support/LogicalResult.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace VM {
+
+// Translates a vm.module to a c module.
+//
+// Exposed via the --iree-vm-ir-to-c-module translation.
+LogicalResult translateModuleToC(IREE::VM::ModuleOp moduleOp,
+                                 llvm::raw_ostream &output);
+LogicalResult translateModuleToC(mlir::ModuleOp outerModuleOp,
+                                 llvm::raw_ostream &output);
+
+}  // namespace VM
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_VM_TARGET_C_CMODULETARGET_H_
diff --git a/iree/compiler/Dialect/VM/Target/C/TranslationRegistration.cpp b/iree/compiler/Dialect/VM/Target/C/TranslationRegistration.cpp
new file mode 100644
index 0000000..bdeea02
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Target/C/TranslationRegistration.cpp
@@ -0,0 +1,34 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "iree/compiler/Dialect/VM/Target/C/CModuleTarget.h"
+#include "mlir/Translation.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace VM {
+
+void registerToCTranslation() {
+  TranslateFromMLIRRegistration toCModule(
+      "iree-vm-ir-to-c-module",
+      [](mlir::ModuleOp moduleOp, llvm::raw_ostream &output) {
+        return translateModuleToC(moduleOp, output);
+      });
+}
+
+}  // namespace VM
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/iree/compiler/Dialect/VM/Target/C/test/CMakeLists.txt b/iree/compiler/Dialect/VM/Target/C/test/CMakeLists.txt
new file mode 100644
index 0000000..6495524
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Target/C/test/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+iree_add_all_subdirs()
+
+file(GLOB _GLOB_X_MLIR LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS *.mlir)
+iree_lit_test_suite(
+  NAME
+    lit
+  SRCS
+    "${_GLOB_X_MLIR}"
+  DATA
+    iree::tools::IreeFileCheck
+    iree::tools::iree-translate
+)
diff --git a/iree/compiler/Dialect/VM/Target/C/test/empty_module.mlir b/iree/compiler/Dialect/VM/Target/C/test/empty_module.mlir
new file mode 100644
index 0000000..60ef77a
--- /dev/null
+++ b/iree/compiler/Dialect/VM/Target/C/test/empty_module.mlir
@@ -0,0 +1,5 @@
+// RUN: iree-translate -iree-vm-ir-to-c-module %s | IreeFileCheck %s
+
+// CHECK: // c module stub
+vm.module @empty_module {
+}
diff --git a/iree/compiler/Dialect/VM/Target/CMakeLists.txt b/iree/compiler/Dialect/VM/Target/CMakeLists.txt
index c2d6a1d..fb9d914 100644
--- a/iree/compiler/Dialect/VM/Target/CMakeLists.txt
+++ b/iree/compiler/Dialect/VM/Target/CMakeLists.txt
@@ -12,8 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# bazel_to_cmake: DO NOT EDIT (EmitC is only buildable with CMake)
+
 iree_add_all_subdirs()
 
+if(IREE_ENABLE_EMITC)
+  set(IREE_VM_CONDITIONAL_TARGETS
+    iree::compiler::Dialect::VM::Target::C
+  )
+endif()
+
 iree_cc_library(
   NAME
     init_targets
@@ -21,5 +29,6 @@
     "init_targets.h"
   DEPS
     iree::compiler::Dialect::VM::Target::Bytecode
+    ${IREE_VM_CONDITIONAL_TARGETS}
   PUBLIC
 )
diff --git a/iree/compiler/Dialect/VM/Target/init_targets.h b/iree/compiler/Dialect/VM/Target/init_targets.h
index 5866a77..6099564 100644
--- a/iree/compiler/Dialect/VM/Target/init_targets.h
+++ b/iree/compiler/Dialect/VM/Target/init_targets.h
@@ -21,6 +21,9 @@
 namespace IREE {
 namespace VM {
 void registerToVMBytecodeTranslation();
+#ifdef IREE_HAVE_EMITC_DIALECT
+void registerToCTranslation();
+#endif  // IREE_HAVE_EMITC_DIALECT
 }  // namespace VM
 }  // namespace IREE
 
@@ -31,6 +34,10 @@
 inline void registerVMTargets() {
   static bool init_once = []() {
     IREE::VM::registerToVMBytecodeTranslation();
+#ifdef IREE_HAVE_EMITC_DIALECT
+    IREE::VM::registerToCTranslation();
+#endif  // IREE_HAVE_EMITC_DIALECT
+
     return true;
   }();
   (void)init_once;
diff --git a/iree/compiler/Translation/CMakeLists.txt b/iree/compiler/Translation/CMakeLists.txt
index dbe2324..e89e46f 100644
--- a/iree/compiler/Translation/CMakeLists.txt
+++ b/iree/compiler/Translation/CMakeLists.txt
@@ -12,8 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# bazel_to_cmake: DO NOT EDIT (EmitC is only buildable with CMake)
+
 iree_add_all_subdirs()
 
+if(IREE_ENABLE_EMITC)
+  set(IREE_VM_CONDITIONAL_TARGETS
+    iree::compiler::Dialect::VM::Target::C
+  )
+endif()
+
 iree_cc_library(
   NAME
     IREEVM
@@ -36,5 +44,6 @@
     iree::compiler::Dialect::VM::Conversion::StandardToVM
     iree::compiler::Dialect::VM::Target::Bytecode
     iree::compiler::Dialect::VM::Transforms
+    ${IREE_VM_CONDITIONAL_TARGETS}
   PUBLIC
 )
diff --git a/iree/compiler/Translation/IREEVM.cpp b/iree/compiler/Translation/IREEVM.cpp
index 8708592..14284eb 100644
--- a/iree/compiler/Translation/IREEVM.cpp
+++ b/iree/compiler/Translation/IREEVM.cpp
@@ -23,6 +23,10 @@
 #include "mlir/Pass/PassManager.h"
 #include "mlir/Translation.h"
 
+#ifdef IREE_HAVE_EMITC_DIALECT
+#include "iree/compiler/Dialect/VM/Target/C/CModuleTarget.h"
+#endif  // IREE_HAVE_EMITC_DIALECT
+
 namespace mlir {
 namespace iree_compiler {
 
@@ -73,10 +77,8 @@
       });
 }
 
-LogicalResult translateFromMLIRToVMBytecodeModule(
-    ModuleOp moduleOp, IREE::HAL::TargetOptions executableOptions,
-    IREE::VM::BytecodeTargetOptions bytecodeOptions,
-    llvm::raw_ostream &output) {
+static LogicalResult translateFromMLIRToVM(
+    ModuleOp moduleOp, IREE::HAL::TargetOptions executableOptions) {
   // Convert from our source to a vm.module in canonical form.
   // After this completes we have a non-bytecode-specific vm.module that we
   // could lower to other forms (LLVM IR, C, etc).
@@ -86,9 +88,22 @@
   IREE::HAL::buildHALTransformPassPipeline(passManager, executableOptions);
   IREE::VM::buildVMTransformPassPipeline(passManager);
   passManager.addPass(mlir::iree_compiler::IREE::createDropCompilerHintsPass());
+
   if (failed(passManager.run(moduleOp))) {
     return moduleOp.emitError() << "conversion from source -> vm failed";
   }
+  return success();
+}
+
+LogicalResult translateFromMLIRToVMBytecodeModule(
+    ModuleOp moduleOp, IREE::HAL::TargetOptions executableOptions,
+    IREE::VM::BytecodeTargetOptions bytecodeOptions,
+    llvm::raw_ostream &output) {
+  auto result = translateFromMLIRToVM(moduleOp, executableOptions);
+
+  if (failed(result)) {
+    return result;
+  }
 
   // Serialize to bytecode.
   return translateModuleToBytecode(moduleOp, bytecodeOptions, output);
@@ -103,10 +118,37 @@
                                              bytecodeTargetOptions, output);
 }
 
+#ifdef IREE_HAVE_EMITC_DIALECT
+LogicalResult translateFromMLIRToVMCModule(
+    ModuleOp moduleOp, IREE::HAL::TargetOptions executableOptions,
+    llvm::raw_ostream &output) {
+  auto result = translateFromMLIRToVM(moduleOp, executableOptions);
+
+  if (failed(result)) {
+    return result;
+  }
+
+  // Serialize to c code.
+  return mlir::iree_compiler::IREE::VM::translateModuleToC(moduleOp, output);
+}
+
+static LogicalResult translateFromMLIRToVMCModuleWithFlags(
+    ModuleOp moduleOp, llvm::raw_ostream &output) {
+  mlir::registerPassManagerCLOptions();
+  auto TargetOptions = IREE::HAL::getTargetOptionsFromFlags();
+  return translateFromMLIRToVMCModule(moduleOp, TargetOptions, output);
+}
+#endif  // IREE_HAVE_EMITC_DIALECT
+
 void registerIREEVMTranslation() {
   TranslateFromMLIRRegistration toVMBytecodeModuleWithFlags(
       "iree-mlir-to-vm-bytecode-module",
       translateFromMLIRToVMBytecodeModuleWithFlags);
+
+#ifdef IREE_HAVE_EMITC_DIALECT
+  TranslateFromMLIRRegistration toVMCModuleWithFlags(
+      "iree-mlir-to-vm-c-module", translateFromMLIRToVMCModuleWithFlags);
+#endif  // IREE_HAVE_EMITC_DIALECT
 }
 
 }  // namespace iree_compiler
diff --git a/iree/compiler/Translation/IREEVM.h b/iree/compiler/Translation/IREEVM.h
index 9b95a31..ed9f36a 100644
--- a/iree/compiler/Translation/IREEVM.h
+++ b/iree/compiler/Translation/IREEVM.h
@@ -53,6 +53,16 @@
     ModuleOp moduleOp, IREE::HAL::TargetOptions executableOptions,
     IREE::VM::BytecodeTargetOptions bytecodeOptions, llvm::raw_ostream &output);
 
+#ifdef IREE_HAVE_EMITC_DIALECT
+// Translates an MLIR module containing a set of supported IREE input dialects
+// to an IREE VM C module.
+//
+// Exposed via the --iree-mlir-to-vm-c-module translation.
+LogicalResult translateFromMLIRToVMCModule(
+    ModuleOp moduleOp, IREE::HAL::TargetOptions executableOptions,
+    llvm::raw_ostream &output);
+#endif  // IREE_HAVE_EMITC_DIALECT
+
 // TODO(benvanik): versions with multiple targets, etc.
 
 void registerIREEVMTransformPassPipeline();
diff --git a/iree/tools/CMakeLists.txt b/iree/tools/CMakeLists.txt
index 2b1fca8..b3a4fa9 100644
--- a/iree/tools/CMakeLists.txt
+++ b/iree/tools/CMakeLists.txt
@@ -24,6 +24,16 @@
     "iree::hal::vulkan::vulkan_driver_module"
 )
 
+if(IREE_ENABLE_EMITC)
+  set(IREE_OPT_CONDITIONAL_DEPS
+    MLIREmitC
+  )
+  set(IREE_TRANSLATE_CONDITIONAL_DEPS
+    MLIREmitC
+    MLIRTargetCpp
+  )
+endif()
+
 iree_cc_binary(
   NAME
     iree-benchmark-module
@@ -255,6 +265,7 @@
       iree::compiler::Dialect::Vulkan::IR
       iree::compiler::Translation::IREEVM
       tensorflow::mlir_hlo
+      ${IREE_OPT_CONDITIONAL_DEPS}
     PUBLIC
   )
 
@@ -280,6 +291,7 @@
       iree::compiler::Dialect::VM::Target::Bytecode
       iree::compiler::Dialect::VM::Target::init_targets
       iree::compiler::Translation::IREEVM
+      ${IREE_TRANSLATE_CONDITIONAL_DEPS}
     PUBLIC
   )
 
diff --git a/iree/tools/opt_main.cc b/iree/tools/opt_main.cc
index 57ebe15..260901c 100644
--- a/iree/tools/opt_main.cc
+++ b/iree/tools/opt_main.cc
@@ -37,6 +37,10 @@
 #include "mlir/Support/FileUtilities.h"
 #include "mlir/Support/MlirOptMain.h"
 
+#ifdef IREE_HAVE_EMITC_DIALECT
+#include "emitc/InitDialect.h"
+#endif  // IREE_HAVE_EMITC_DIALECT
+
 static llvm::cl::opt<std::string> inputFilename(llvm::cl::Positional,
                                                 llvm::cl::desc("<input file>"),
                                                 llvm::cl::init("-"));
@@ -74,6 +78,9 @@
 int main(int argc, char **argv) {
   mlir::registerMlirDialects();
   mlir::registerMlirPasses();
+#ifdef IREE_HAVE_EMITC_DIALECT
+  mlir::registerEmitCDialect();
+#endif  // IREE_HAVE_EMITC_DIALECT
   mlir::registerXLADialects();
   mlir::iree_compiler::registerIreeDialects();
   mlir::iree_compiler::registerIreeCompilerModuleDialects();
diff --git a/iree/tools/translate_main.cc b/iree/tools/translate_main.cc
index 97b4334..50edac7 100644
--- a/iree/tools/translate_main.cc
+++ b/iree/tools/translate_main.cc
@@ -38,6 +38,11 @@
 #include "mlir/Support/ToolUtilities.h"
 #include "mlir/Translation.h"
 
+#ifdef IREE_HAVE_EMITC_DIALECT
+#include "emitc/InitDialect.h"
+#include "emitc/InitTranslation.h"
+#endif  // IREE_HAVE_EMITC_DIALECT
+
 static llvm::cl::opt<std::string> inputFilename(llvm::cl::Positional,
                                                 llvm::cl::desc("<input file>"),
                                                 llvm::cl::init("-"));
@@ -56,12 +61,18 @@
   llvm::InitLLVM y(argc, argv);
 
   mlir::registerMlirDialects();
+#ifdef IREE_HAVE_EMITC_DIALECT
+  mlir::registerEmitCDialect();
+#endif  // IREE_HAVE_EMITC_DIALECT
   mlir::registerXLADialects();
   mlir::iree_compiler::registerIreeDialects();
   mlir::iree_compiler::registerIreeCompilerModuleDialects();
   mlir::iree_compiler::registerHALTargetBackends();
   mlir::iree_compiler::registerVMTargets();
   mlir::registerMlirTranslations();
+#ifdef IREE_HAVE_EMITC_DIALECT
+  mlir::registerEmitCTranslation();
+#endif  // IREE_HAVE_EMITC_DIALECT
   mlir::iree_compiler::registerIreeTranslations();
   mlir::iree_compiler::registerLinalgToSPIRVPasses();
 
diff --git a/third_party/mlir-emitc b/third_party/mlir-emitc
index 67f3cce..80885f8 160000
--- a/third_party/mlir-emitc
+++ b/third_party/mlir-emitc
@@ -1 +1 @@
-Subproject commit 67f3ccebee84f3488b46a8d3ac005178c52ff264
+Subproject commit 80885f899e12d55a45561ef758eea47bb340dbf1