[pydm] Plumb through RTL and finish conversions for basic program. (#7244)

* I haven't yet figured out the best way to e2e test this. For now, putting e2e tests in the samples repo: https://github.com/google/iree-samples/tree/main/pydm/simple
diff --git a/bindings/python/iree/runtime/vm.cc b/bindings/python/iree/runtime/vm.cc
index ae39185..d49cf01 100644
--- a/bindings/python/iree/runtime/vm.cc
+++ b/bindings/python/iree/runtime/vm.cc
@@ -690,6 +690,30 @@
       .def_property_readonly(
           "stashed_flatbuffer_blob",
           [](VmModule& self) { return self.get_stashed_flatbuffer_blob(); })
+      .def_property_readonly(
+          "function_names",
+          [](VmModule& self) {
+            py::list names;
+            iree_vm_module_signature_t sig =
+                iree_vm_module_signature(self.raw_ptr());
+            for (size_t ordinal = 0; ordinal < sig.export_function_count;
+                 ++ordinal) {
+              iree_vm_function_t f;
+              iree_string_view_t linkage_name;
+              auto status = iree_vm_module_lookup_function_by_ordinal(
+                  self.raw_ptr(), IREE_VM_FUNCTION_LINKAGE_EXPORT, ordinal, &f,
+                  &linkage_name);
+              if (iree_status_is_not_found(status)) {
+                iree_status_ignore(status);
+                break;
+              }
+              CheckApiStatus(status, "Error enumerating module");
+              iree_string_view_t fname = iree_vm_function_name(&f);
+              py::str name(fname.data, fname.size);
+              names.append(name);
+            }
+            return names;
+          })
       .def("__repr__", [](VmModule& self) {
         std::string repr("<VmModule ");
         iree_string_view_t name = iree_vm_module_name(self.raw_ptr());
diff --git a/llvm-external-projects/iree-compiler-api/include/iree-compiler-c/Compiler.h b/llvm-external-projects/iree-compiler-api/include/iree-compiler-c/Compiler.h
index 6e946e8..2be688c 100644
--- a/llvm-external-projects/iree-compiler-api/include/iree-compiler-c/Compiler.h
+++ b/llvm-external-projects/iree-compiler-api/include/iree-compiler-c/Compiler.h
@@ -21,6 +21,7 @@
   typedef struct name name
 
 DEFINE_C_API_STRUCT(IreeCompilerOptions, void);
+#undef DEFINE_C_API_STRUCT
 
 //===----------------------------------------------------------------------===//
 // Registration.
diff --git a/llvm-external-projects/iree-compiler-api/lib/CAPI/Compiler.cpp b/llvm-external-projects/iree-compiler-api/lib/CAPI/Compiler.cpp
index 3b6ae8d..d13028d 100644
--- a/llvm-external-projects/iree-compiler-api/lib/CAPI/Compiler.cpp
+++ b/llvm-external-projects/iree-compiler-api/lib/CAPI/Compiler.cpp
@@ -44,7 +44,10 @@
 void ireeCompilerRegisterTargetBackends() { registerHALTargetBackends(); }
 
 IreeCompilerOptions ireeCompilerOptionsCreate() {
-  return wrap(new CompilerOptions);
+  auto options = new CompilerOptions;
+  // TODO: Make configurable.
+  options->vmTargetOptions.f32Extension = true;
+  return wrap(options);
 }
 
 void ireeCompilerOptionsDestroy(IreeCompilerOptions options) {
diff --git a/llvm-external-projects/iree-dialects/BUILD b/llvm-external-projects/iree-dialects/BUILD
index df10347..ff9cbdf 100644
--- a/llvm-external-projects/iree-dialects/BUILD
+++ b/llvm-external-projects/iree-dialects/BUILD
@@ -312,5 +312,6 @@
         ":IREEPyDMTransforms",
         "@llvm-project//mlir:CAPIIR",
         "@llvm-project//mlir:IR",
+        "@llvm-project//mlir:Support",
     ],
 )
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects-c/Dialects.h b/llvm-external-projects/iree-dialects/include/iree-dialects-c/Dialects.h
index 613df95..da8701a 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects-c/Dialects.h
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects-c/Dialects.h
@@ -27,6 +27,28 @@
 
 MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(IREEPyDM, iree_pydm);
 
+#define DEFINE_C_API_STRUCT(name, storage) \
+  struct name {                            \
+    storage *ptr;                          \
+  };                                       \
+  typedef struct name name
+
+DEFINE_C_API_STRUCT(IREEPyDMSourceBundle, void);
+DEFINE_C_API_STRUCT(IREEPyDMLoweringOptions, void);
+#undef DEFINE_C_API_STRUCT
+
+/// Creates a PyDM source bundle from an ASM string.
+MLIR_CAPI_EXPORTED IREEPyDMSourceBundle
+ireePyDMSourceBundleCreateAsm(MlirStringRef asmString);
+
+/// Creates a PyDM source bundle from a file path.
+MLIR_CAPI_EXPORTED IREEPyDMSourceBundle
+ireePyDMSourceBundleCreateFile(MlirStringRef filePath);
+
+/// Destroys a created source bundle.
+MLIR_CAPI_EXPORTED void ireePyDMSourceBundleDestroy(
+    IREEPyDMSourceBundle bundle);
+
 MLIR_CAPI_EXPORTED bool mlirTypeIsAIREEPyDMPrimitiveType(MlirType type);
 
 #define IREEPYDM_DECLARE_NULLARY_TYPE(Name)                         \
@@ -51,9 +73,20 @@
 MLIR_CAPI_EXPORTED MlirType mlirIREEPyDMObjectTypeGet(MlirContext context,
                                                       MlirType primitive);
 
+/// Creates a lowering options struct.
+MLIR_CAPI_EXPORTED IREEPyDMLoweringOptions ireePyDMLoweringOptionsCreate();
+
+/// Sets the RTL link source bundle to the lowering options.
+MLIR_CAPI_EXPORTED void ireePyDMLoweringOptionsLinkRtl(
+    IREEPyDMLoweringOptions options, IREEPyDMSourceBundle source);
+
+/// Destroys a created lowering options struct.
+MLIR_CAPI_EXPORTED void ireePyDMLoweringOptionsDestroy(
+    IREEPyDMLoweringOptions options);
+
 /// Builds a pass pipeline which lowers the iree_pydm dialect to IREE.
 MLIR_CAPI_EXPORTED void mlirIREEPyDMBuildLowerToIREEPassPipeline(
-    MlirOpPassManager passManager);
+    MlirOpPassManager passManager, IREEPyDMLoweringOptions options);
 
 #ifdef __cplusplus
 }
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/Transforms/Passes.h b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/Transforms/Passes.h
index c3670e0..a169051 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/Transforms/Passes.h
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/Transforms/Passes.h
@@ -7,8 +7,11 @@
 #ifndef IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_TRANSFORMS_PASSES_H
 #define IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_TRANSFORMS_PASSES_H
 
+#include <memory>
+
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/Pass/Pass.h"
+#include "mlir/Support/LLVM.h"
 
 namespace mlir {
 
@@ -18,9 +21,25 @@
 
 namespace iree_pydm {
 
+/// References sources, either passed literally or by reference to a file.
+/// One of `asmBlob` or `asmFilePath` should be populated.
+struct SourceBundle {
+  std::shared_ptr<std::string> asmBlob;
+  Optional<std::string> asmFilePath;
+};
+
+/// Options for lowering to IREE.
+struct LowerToIREEOptions {
+  Optional<SourceBundle> linkRtlSource;
+};
+
 std::unique_ptr<OperationPass<ModuleOp>> createConvertIREEPyDMToIREEPass();
 std::unique_ptr<OperationPass<ModuleOp>> createLowerIREEPyDMToRTLPass();
-std::unique_ptr<OperationPass<ModuleOp>> createLinkIREEPyDMRTLPass();
+std::unique_ptr<OperationPass<ModuleOp>> createLinkIREEPyDMRTLPass(
+    Optional<SourceBundle> linkRtlSourceBundle = None);
+
+void buildLowerToIREEPassPipeline(OpPassManager& passManager,
+                                  const LowerToIREEOptions& options);
 
 #define GEN_PASS_REGISTRATION
 #include "iree-dialects/Dialect/IREEPyDM/Transforms/Passes.h.inc"
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/Transforms/ToIREE/TypeConverter.h b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/Transforms/ToIREE/TypeConverter.h
index 69dd0e1..daef405 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/Transforms/ToIREE/TypeConverter.h
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/Transforms/ToIREE/TypeConverter.h
@@ -26,6 +26,10 @@
   Type getWeakIntegerType(Builder b) const;
   Type getWeakFloatType(Builder b) const;
 
+  // Whether the given type is a valid lowered type.
+  bool isTypeLegal(Type t) const;
+  bool areTypesLegal(TypeRange types) const;
+
  private:
   bool boolBits = 32;
   int weakIntegerBits = 32;
diff --git a/llvm-external-projects/iree-dialects/lib/CAPI/CMakeLists.txt b/llvm-external-projects/iree-dialects/lib/CAPI/CMakeLists.txt
index 66682e5..75be3ec 100644
--- a/llvm-external-projects/iree-dialects/lib/CAPI/CMakeLists.txt
+++ b/llvm-external-projects/iree-dialects/lib/CAPI/CMakeLists.txt
@@ -5,7 +5,7 @@
   MLIRIR
   IREEDialectsIREEDialect
   IREEDialectsIREEPyDMDialect
-  IREEDialectsIREEPyDMToIREEPasses
+  IREEDialectsIREEPyDMPasses
 )
 
 iree_dialects_target_includes(IREEDialectsCAPI)
diff --git a/llvm-external-projects/iree-dialects/lib/CAPI/Dialects.cpp b/llvm-external-projects/iree-dialects/lib/CAPI/Dialects.cpp
index ad5e546..d316fbe 100644
--- a/llvm-external-projects/iree-dialects/lib/CAPI/Dialects.cpp
+++ b/llvm-external-projects/iree-dialects/lib/CAPI/Dialects.cpp
@@ -15,6 +15,9 @@
 #include "mlir/CAPI/Support.h"
 #include "mlir/CAPI/Utils.h"
 #include "mlir/CAPI/Wrap.h"
+#include "mlir/Support/LLVM.h"
+
+using namespace mlir;
 
 //===----------------------------------------------------------------------===//
 // IREEDialect
@@ -29,6 +32,10 @@
 MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(IREEPyDM, iree_pydm,
                                       mlir::iree_pydm::IREEPyDMDialect)
 
+DEFINE_C_API_PTR_METHODS(IREEPyDMSourceBundle, mlir::iree_pydm::SourceBundle)
+DEFINE_C_API_PTR_METHODS(IREEPyDMLoweringOptions,
+                         mlir::iree_pydm::LowerToIREEOptions)
+
 bool mlirTypeIsAIREEPyDMPrimitiveType(MlirType type) {
   return unwrap(type).isa<mlir::iree_pydm::PrimitiveType>();
 }
@@ -66,8 +73,40 @@
   return wrap(mlir::iree_pydm::ObjectType::get(unwrap(ctx), cppType));
 }
 
-void mlirIREEPyDMBuildLowerToIREEPassPipeline(MlirOpPassManager passManager) {
+void mlirIREEPyDMBuildLowerToIREEPassPipeline(MlirOpPassManager passManager,
+                                              IREEPyDMLoweringOptions options) {
   auto *passManagerCpp = unwrap(passManager);
-  // TODO: Should be a pass pipeline, not loose passes in the C impl.
-  passManagerCpp->addPass(mlir::iree_pydm::createConvertIREEPyDMToIREEPass());
+  mlir::iree_pydm::buildLowerToIREEPassPipeline(*passManagerCpp,
+                                                *unwrap(options));
+}
+
+// SourceBundle
+IREEPyDMSourceBundle ireePyDMSourceBundleCreateAsm(MlirStringRef asmString) {
+  auto bundle = std::make_unique<mlir::iree_pydm::SourceBundle>();
+  bundle->asmBlob = std::make_shared<std::string>(unwrap(asmString));
+  return wrap(bundle.release());
+}
+
+IREEPyDMSourceBundle ireePyDMSourceBundleCreateFile(MlirStringRef filePath) {
+  auto bundle = std::make_unique<mlir::iree_pydm::SourceBundle>();
+  bundle->asmFilePath = std::string(unwrap(filePath));
+  return wrap(bundle.release());
+}
+
+void ireePyDMSourceBundleDestroy(IREEPyDMSourceBundle bundle) {
+  delete unwrap(bundle);
+}
+
+// LoweringOptions
+IREEPyDMLoweringOptions ireePyDMLoweringOptionsCreate() {
+  return wrap(new mlir::iree_pydm::LowerToIREEOptions);
+}
+
+void ireePyDMLoweringOptionsLinkRtl(IREEPyDMLoweringOptions options,
+                                    IREEPyDMSourceBundle source) {
+  unwrap(options)->linkRtlSource = *unwrap(source);
+}
+
+void ireePyDMLoweringOptionsDestroy(IREEPyDMLoweringOptions options) {
+  delete unwrap(options);
 }
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/CMakeLists.txt b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/CMakeLists.txt
index 37db9a5..44fe3b2 100644
--- a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/CMakeLists.txt
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/CMakeLists.txt
@@ -1,2 +1,15 @@
 add_subdirectory(RTL)
 add_subdirectory(ToIREE)
+
+add_mlir_library(IREEDialectsIREEPyDMPasses
+  Passes.cpp
+
+  DEPENDS
+  MLIRIREEPyDMTransformsPassesIncGen
+
+  LINK_LIBS PUBLIC
+  IREEDialectsIREEPyDMRTLPasses
+  IREEDialectsIREEPyDMToIREEPasses
+)
+
+iree_dialects_target_includes(IREEDialectsIREEPyDMPasses)
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/Passes.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/Passes.cpp
new file mode 100644
index 0000000..caaaf14
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/Passes.cpp
@@ -0,0 +1,22 @@
+// Copyright 2021 The IREE Authors
+//
+// Licensed under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "iree-dialects/Dialect/IREEPyDM/Transforms/Passes.h"
+
+#include "mlir/Pass/PassManager.h"
+
+using namespace mlir;
+using namespace mlir::iree_pydm;
+
+void mlir::iree_pydm::buildLowerToIREEPassPipeline(
+    OpPassManager& passManager, const LowerToIREEOptions& options) {
+  // TODO: Needs to be iterative, support optimization passes, etc.
+  passManager.addPass(createLowerIREEPyDMToRTLPass());
+  if (options.linkRtlSource) {
+    passManager.addPass(createLinkIREEPyDMRTLPass(options.linkRtlSource));
+  }
+  passManager.addPass(createConvertIREEPyDMToIREEPass());
+}
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/RTL/LinkRTLPass.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/RTL/LinkRTLPass.cpp
index 9608edf..7803d65 100644
--- a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/RTL/LinkRTLPass.cpp
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/RTL/LinkRTLPass.cpp
@@ -35,22 +35,38 @@
 namespace {
 
 class LinkIREEPyDMRTLPass : public LinkIREEPyDMRTLBase<LinkIREEPyDMRTLPass> {
+ public:
+  LinkIREEPyDMRTLPass() = default;
+  LinkIREEPyDMRTLPass(Optional<SourceBundle> linkRtlSourceBundle)
+      : linkRtlSourceBundle(std::move(linkRtlSourceBundle)) {}
+
+ private:
   LogicalResult initialize(MLIRContext *context) override {
-    // Already initialized in some way.
-    if (!rtlModule && rtlFile.empty()) {
+    SourceBundle localSource;
+    if (linkRtlSourceBundle) {
+      localSource = *linkRtlSourceBundle;
+    } else {
+      // Get it from the cli options.
+      localSource.asmFilePath = rtlFile;
+    }
+
+    if (localSource.asmBlob) {
+      // Parse from inline asm.
+      auto owningOp = parseSourceString(*localSource.asmBlob, context);
+      if (!owningOp) return failure();
+      rtlModule = std::make_shared<OwningModuleRef>(std::move(owningOp));
+    } else if (localSource.asmFilePath) {
+      // Parse from a file.
+      auto owningOp = parseSourceFile(*localSource.asmFilePath, context);
+      if (!owningOp) return failure();
+      rtlModule = std::make_shared<OwningModuleRef>(std::move(owningOp));
+    } else {
       return emitError(UnknownLoc::get(context))
              << "pass " << getArgument()
              << "must be initialized with an RTL module (did you mean to "
                 "add an rtl-file option?)";
     }
 
-    if (!rtlFile.empty()) {
-      // Parse from a file.
-      auto owningOp = parseSourceFile(rtlFile, context);
-      if (!owningOp) return failure();
-      rtlModule = std::make_shared<OwningModuleRef>(std::move(owningOp));
-    }
-
     ModuleOp parentModule = rtlModule->get();
     // Walk the module and build a SymbolTable for each sub-module.
     parentModule->walk([&](ModuleOp importModule) {
@@ -187,11 +203,15 @@
 
   // A SymbolTable for each sub module.
   SmallVector<SymbolTable> importModules;
+
+  // ASM source of RTL modules to link (otherwise will use pass options).
+  Optional<SourceBundle> linkRtlSourceBundle;
 };
 
 }  // namespace
 
 std::unique_ptr<OperationPass<ModuleOp>>
-mlir::iree_pydm::createLinkIREEPyDMRTLPass() {
-  return std::make_unique<LinkIREEPyDMRTLPass>();
+mlir::iree_pydm::createLinkIREEPyDMRTLPass(
+    Optional<SourceBundle> linkRtlSourceBundle) {
+  return std::make_unique<LinkIREEPyDMRTLPass>(std::move(linkRtlSourceBundle));
 }
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/ConversionPass.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/ConversionPass.cpp
index 661a237..2d4d88d 100644
--- a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/ConversionPass.cpp
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/ConversionPass.cpp
@@ -40,6 +40,16 @@
     target.addLegalDialect<mlir::iree::IREEDialect>();
     target.addLegalDialect<mlir::math::MathDialect>();
     target.addLegalDialect<StandardOpsDialect>();
+
+    // Some CFG ops can be present in the original pydm program. Need to
+    // verify legality based on types.
+    target.addDynamicallyLegalOp<BranchOp>([&](BranchOp op) -> bool {
+      return typeConverter.areTypesLegal(op.getOperandTypes());
+    });
+    target.addDynamicallyLegalOp<CondBranchOp>([&](CondBranchOp op) -> bool {
+      return typeConverter.areTypesLegal(op.getOperandTypes());
+    });
+
     if (failed(applyPartialConversion(moduleOp, target, std::move(patterns)))) {
       return signalPassFailure();
     }
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/LoweringPatterns.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/LoweringPatterns.cpp
index 6214709..9b3e1fd 100644
--- a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/LoweringPatterns.cpp
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/LoweringPatterns.cpp
@@ -17,6 +17,7 @@
 using namespace mlir;
 using namespace mlir::iree_pydm;
 
+namespace arith_d = mlir;
 namespace iree_d = mlir::iree;
 namespace builtin_d = mlir;
 namespace std_d = mlir;
@@ -101,6 +102,60 @@
   return list;
 }
 
+static Value castIntegerValue(Location loc, Value input,
+                              builtin_d::IntegerType resultType,
+                              OpBuilder &builder) {
+  builtin_d::IntegerType inputType =
+      input.getType().cast<builtin_d::IntegerType>();
+  if (inputType.getWidth() == resultType.getWidth()) {
+    return input;
+  } else if (inputType.getWidth() < resultType.getWidth()) {
+    return builder.create<arith_d::SignExtendIOp>(loc, resultType, input);
+  } else {
+    return builder.create<arith_d::TruncateIOp>(loc, resultType, input);
+  }
+}
+
+static Optional<arith_d::CmpIPredicate> convertIntegerComparePredicate(
+    StringAttr dunderName, bool isSigned, Builder &builder) {
+  StringRef v = dunderName.getValue();
+  if (v == "lt") {
+    return isSigned ? arith_d::CmpIPredicate::slt : arith_d::CmpIPredicate::ult;
+  } else if (v == "le") {
+    return isSigned ? arith_d::CmpIPredicate::sle : arith_d::CmpIPredicate::ule;
+  } else if (v == "eq" || v == "is") {
+    return arith_d::CmpIPredicate::eq;
+  } else if (v == "ne" || v == "isnot") {
+    return arith_d::CmpIPredicate::ne;
+  } else if (v == "gt") {
+    return isSigned ? arith_d::CmpIPredicate::sgt : arith_d::CmpIPredicate::ugt;
+  } else if (v == "ge") {
+    return isSigned ? arith_d::CmpIPredicate::sge : arith_d::CmpIPredicate::uge;
+  }
+
+  return {};
+}
+
+static Optional<arith_d::CmpFPredicate> convertFpComparePredicate(
+    StringAttr dunderName, Builder &builder) {
+  StringRef v = dunderName.getValue();
+  if (v == "lt") {
+    return arith_d::CmpFPredicate::OLT;
+  } else if (v == "le") {
+    return arith_d::CmpFPredicate::OLE;
+  } else if (v == "eq" || v == "is") {
+    return arith_d::CmpFPredicate::OEQ;
+  } else if (v == "ne" || v == "isnot") {
+    return arith_d::CmpFPredicate::ONE;
+  } else if (v == "gt") {
+    return arith_d::CmpFPredicate::OGT;
+  } else if (v == "ge") {
+    return arith_d::CmpFPredicate::OGE;
+  }
+
+  return {};
+}
+
 namespace {
 
 class AllocFreeVarOpConversion
@@ -120,6 +175,53 @@
   }
 };
 
+class ApplyCompareNumericConversion
+    : public OpConversionPattern<pydm_d::ApplyCompareOp> {
+  using OpConversionPattern::OpConversionPattern;
+
+  LogicalResult matchAndRewrite(
+      pydm_d::ApplyCompareOp srcOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    Type leftType = adaptor.left().getType();
+    Type rightType = adaptor.right().getType();
+    if (leftType != rightType) {
+      return rewriter.notifyMatchFailure(srcOp, "not same type operands");
+    }
+    if (leftType.isa<builtin_d::IntegerType>()) {
+      bool isSigned = true;  // TODO: Unsigned.
+      auto predicate = convertIntegerComparePredicate(adaptor.dunder_name(),
+                                                      isSigned, rewriter);
+      if (!predicate)
+        return rewriter.notifyMatchFailure(srcOp, "unsupported predicate");
+      rewriter.replaceOpWithNewOp<arith_d::CmpIOp>(
+          srcOp, *predicate, adaptor.left(), adaptor.right());
+      return success();
+    } else if (leftType.isa<builtin_d::FloatType>()) {
+      auto predicate =
+          convertFpComparePredicate(adaptor.dunder_name(), rewriter);
+      if (!predicate)
+        return rewriter.notifyMatchFailure(srcOp, "unsupported predicate");
+      rewriter.replaceOpWithNewOp<arith_d::CmpFOp>(
+          srcOp, *predicate, adaptor.left(), adaptor.right());
+      return success();
+    }
+
+    return rewriter.notifyMatchFailure(srcOp, "non numeric type");
+  }
+};
+
+class BoolToPredConversion : public OpConversionPattern<pydm_d::BoolToPredOp> {
+  using OpConversionPattern::OpConversionPattern;
+
+  LogicalResult matchAndRewrite(
+      pydm_d::BoolToPredOp srcOp, ArrayRef<Value> operands,
+      ConversionPatternRewriter &rewriter) const override {
+    pydm_d::BoolToPredOp::Adaptor adaptor(operands);
+    rewriter.replaceOp(srcOp, adaptor.value());
+    return success();
+  }
+};
+
 class BoxOpConversion : public OpConversionPattern<pydm_d::BoxOp> {
   using OpConversionPattern::OpConversionPattern;
 
@@ -140,6 +242,95 @@
   }
 };
 
+class BranchConversion : public OpConversionPattern<std_d::BranchOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      std_d::BranchOp srcOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<std_d::BranchOp>(srcOp, srcOp.dest(),
+                                                 adaptor.destOperands());
+    return success();
+  }
+};
+
+class CallOpConversion : public OpConversionPattern<pydm_d::CallOp> {
+  using OpConversionPattern::OpConversionPattern;
+
+  LogicalResult matchAndRewrite(
+      pydm_d::CallOp srcOp, ArrayRef<Value> operands,
+      ConversionPatternRewriter &rewriter) const override {
+    pydm_d::CallOp::Adaptor adaptor(operands);
+    SmallVector<Type> resultTypes;
+    if (failed(getTypeConverter()->convertTypes(srcOp.getResultTypes(),
+                                                resultTypes))) {
+      return rewriter.notifyMatchFailure(srcOp,
+                                         "result types could not be converted");
+    }
+    rewriter.replaceOpWithNewOp<std_d::CallOp>(srcOp, srcOp.callee(),
+                                               resultTypes, adaptor.operands());
+    return success();
+  }
+};
+
+class CondBranchConversion : public OpConversionPattern<std_d::CondBranchOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      std_d::CondBranchOp srcOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<std_d::CondBranchOp>(
+        srcOp, adaptor.condition(), srcOp.trueDest(),
+        adaptor.trueDestOperands(), srcOp.falseDest(),
+        adaptor.falseDestOperands());
+    return success();
+  }
+};
+
+class ConstantOpConversion : public OpConversionPattern<pydm_d::ConstantOp> {
+  using OpConversionPattern::OpConversionPattern;
+
+  LogicalResult matchAndRewrite(
+      pydm_d::ConstantOp srcOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto loc = srcOp.getLoc();
+    Type resultType = typeConverter->convertType(srcOp.getResult().getType());
+    if (!resultType)
+      return rewriter.notifyMatchFailure(
+          srcOp, "constant type could not be converted");
+    Attribute newValue = adaptor.value();
+    // Fixup widths of integer types that may be wider/narrower than the
+    // stored attribute (which tends to be stored in high precision in pydm
+    // constants).
+    TypeSwitch<Type>(resultType)
+        .Case([&](builtin_d::IntegerType t) {
+          APInt intValue =
+              newValue.cast<IntegerAttr>().getValue().sextOrTrunc(t.getWidth());
+          newValue = rewriter.getIntegerAttr(t, intValue);
+        })
+        .Case([&](builtin_d::FloatType t) {
+          APFloat fpValue = newValue.cast<FloatAttr>().getValue();
+          if (APFloat::SemanticsToEnum(fpValue.getSemantics()) !=
+              APFloat::SemanticsToEnum(t.getFloatSemantics())) {
+            // Convert.
+            APFloat newFpValue = fpValue;
+            bool losesInfo;
+            newFpValue.convert(t.getFloatSemantics(),
+                               APFloat::rmNearestTiesToEven, &losesInfo);
+            if (losesInfo) {
+              emitWarning(loc) << "conversion of " << newValue << " to " << t
+                               << " loses information";
+            }
+            newValue = rewriter.getFloatAttr(t, newFpValue);
+          }
+        });
+
+    if (!newValue)
+      return rewriter.notifyMatchFailure(
+          srcOp, "constant cannot be represented as a standard constant");
+    rewriter.replaceOpWithNewOp<std_d::ConstantOp>(srcOp, resultType, newValue);
+    return success();
+  }
+};
+
 class FuncOpConversion : public OpConversionPattern<pydm_d::FuncOp> {
   using OpConversionPattern::OpConversionPattern;
 
@@ -172,6 +363,7 @@
         convertedResultTypes);
     auto newFuncOp = rewriter.create<mlir::FuncOp>(
         srcOp.getLoc(), srcOp.getName(), newFuncType);
+    newFuncOp.setVisibility(srcOp.getVisibility());
     rewriter.inlineRegionBefore(srcOp.getBody(), newFuncOp.getBody(),
                                 newFuncOp.end());
 
@@ -187,6 +379,33 @@
   }
 };
 
+class GetTypeCodeConversion
+    : public OpConversionPattern<pydm_d::GetTypeCodeOp> {
+  using OpConversionPattern::OpConversionPattern;
+
+  LogicalResult matchAndRewrite(
+      pydm_d::GetTypeCodeOp srcOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto loc = srcOp.getLoc();
+    // Gets the 0'th element of the object list, optionally casting it to the
+    // converted integer type.
+    Type resultType = typeConverter->convertType(srcOp.getResult().getType());
+    if (!resultType)
+      return rewriter.notifyMatchFailure(srcOp,
+                                         "result type could not be converted");
+    Type i32Type = rewriter.getIntegerType(32);
+    Value index0 =
+        rewriter.create<std_d::ConstantOp>(loc, rewriter.getIndexAttr(0));
+    Value typeCode = rewriter.create<iree_d::ListGetOp>(
+        loc, i32Type, adaptor.value(), index0);
+    rewriter.replaceOp(
+        srcOp,
+        castIntegerValue(loc, typeCode,
+                         resultType.cast<builtin_d::IntegerType>(), rewriter));
+    return success();
+  }
+};
+
 class LoadVarOpConversion : public OpConversionPattern<pydm_d::LoadVarOp> {
   using OpConversionPattern::OpConversionPattern;
 
@@ -237,8 +456,8 @@
     auto loc = srcOp.getLoc();
 
     Value status = operands[0];
-    // Get the containing function return type so that we can create a suitable
-    // null return value.
+    // Get the containing function return type so that we can create a
+    // suitable null return value.
     auto parentFunc = srcOp->getParentOfType<builtin_d::FuncOp>();
     if (!parentFunc)
       return rewriter.notifyMatchFailure(srcOp, "not contained by a func");
@@ -390,10 +609,13 @@
     MLIRContext *context, TypeConverter &typeConverter,
     RewritePatternSet &patterns) {
   // Structural.
-  patterns.insert<AllocFreeVarOpConversion, BoxOpConversion, FuncOpConversion,
-                  LoadVarOpConversion, RaiseOnFailureOpConversion,
-                  ReturnOpConversion, StoreVarOpConversion, UnboxOpConversion>(
-      typeConverter, context);
+  patterns.insert<AllocFreeVarOpConversion, ApplyCompareNumericConversion,
+                  BoolToPredConversion, BoxOpConversion, BranchConversion,
+                  CallOpConversion, CondBranchConversion, ConstantOpConversion,
+                  FuncOpConversion, GetTypeCodeConversion, LoadVarOpConversion,
+                  RaiseOnFailureOpConversion, ReturnOpConversion,
+                  StoreVarOpConversion, UnboxOpConversion>(typeConverter,
+                                                           context);
 
   // Constants and constructors.
   patterns.insert<NoneOpConversion>(typeConverter, context);
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/TypeConverter.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/TypeConverter.cpp
index 9416b2a..0ee4fb7 100644
--- a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/TypeConverter.cpp
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/Transforms/ToIREE/TypeConverter.cpp
@@ -37,12 +37,23 @@
     return getVariantListType(b);
   });
 
+  // Bool.
+  addConversion([&](pydm_d::BoolType t) -> Optional<Type> {
+    return builtin_d::IntegerType::get(t.getContext(), 1);
+  });
+
   // Integer type hierarchy.
   addConversion([&](pydm_d::IntegerType t) -> Optional<Type> {
     Builder b(t.getContext());
     return getWeakIntegerType(b);
   });
 
+  // Real type hierarchy.
+  addConversion([&](pydm_d::RealType t) -> Optional<Type> {
+    Builder b(t.getContext());
+    return getWeakFloatType(b);
+  });
+
   // Variable references.
   addConversion([](pydm_d::FreeVarRefType t) -> Optional<Type> {
     // Just an object record.
@@ -73,3 +84,15 @@
       return b.getF64Type();
   }
 }
+
+bool LoweringTypeConverter::isTypeLegal(Type t) const {
+  return t.isa<builtin_d::IntegerType, builtin_d::FloatType,
+               builtin_d::IndexType, iree_d::ListType>();
+}
+
+bool LoweringTypeConverter::areTypesLegal(TypeRange types) const {
+  for (Type t : types) {
+    if (!isTypeLegal(t)) return false;
+  }
+  return true;
+}
diff --git a/llvm-external-projects/iree-dialects/python/IREEDialectsModule.cpp b/llvm-external-projects/iree-dialects/python/IREEDialectsModule.cpp
index 0b235ee..067c565 100644
--- a/llvm-external-projects/iree-dialects/python/IREEDialectsModule.cpp
+++ b/llvm-external-projects/iree-dialects/python/IREEDialectsModule.cpp
@@ -15,6 +15,36 @@
 namespace py = pybind11;
 using namespace mlir::python::adaptors;
 
+namespace {
+
+struct PyIREEPyDMSourceBundle {
+  PyIREEPyDMSourceBundle(IREEPyDMSourceBundle wrapped) : wrapped(wrapped) {}
+  PyIREEPyDMSourceBundle(PyIREEPyDMSourceBundle &&other)
+      : wrapped(other.wrapped) {
+    other.wrapped.ptr = nullptr;
+  }
+  PyIREEPyDMSourceBundle(const PyIREEPyDMSourceBundle &) = delete;
+  ~PyIREEPyDMSourceBundle() {
+    if (wrapped.ptr) ireePyDMSourceBundleDestroy(wrapped);
+  }
+  IREEPyDMSourceBundle wrapped;
+};
+
+struct PyIREEPyDMLoweringOptions {
+  PyIREEPyDMLoweringOptions() : wrapped(ireePyDMLoweringOptionsCreate()) {}
+  PyIREEPyDMLoweringOptions(PyIREEPyDMLoweringOptions &&other)
+      : wrapped(other.wrapped) {
+    other.wrapped.ptr = nullptr;
+  }
+  PyIREEPyDMLoweringOptions(const PyIREEPyDMLoweringOptions &) = delete;
+  ~PyIREEPyDMLoweringOptions() {
+    if (wrapped.ptr) ireePyDMLoweringOptionsDestroy(wrapped);
+  }
+  IREEPyDMLoweringOptions wrapped;
+};
+
+}  // namespace
+
 PYBIND11_MODULE(_ireeDialects, m) {
   m.doc() = "iree-dialects main python extension";
 
@@ -63,6 +93,37 @@
   //===--------------------------------------------------------------------===//
   auto iree_pydm_m = m.def_submodule("iree_pydm");
 
+  py::class_<PyIREEPyDMSourceBundle>(
+      iree_pydm_m, "SourceBundle", py::module_local(),
+      "Contains raw assembly source or a reference to a file")
+      .def_static(
+          "from_asm",
+          [](std::string asmBlob) {
+            return PyIREEPyDMSourceBundle(ireePyDMSourceBundleCreateAsm(
+                {asmBlob.data(), asmBlob.size()}));
+          },
+          py::arg("asm_blob"),
+          "Creates a SourceBundle from an ASM blob (string or bytes)")
+      .def_static(
+          "from_file",
+          [](std::string asmFile) {
+            return PyIREEPyDMSourceBundle(ireePyDMSourceBundleCreateFile(
+                {asmFile.data(), asmFile.size()}));
+          },
+          py::arg("asm_file"),
+          "Creates a SourceBundle from a file containing ASM");
+  py::class_<PyIREEPyDMLoweringOptions>(iree_pydm_m, "LoweringOptions",
+                                        py::module_local(),
+                                        "Lowering options to compile to IREE")
+      .def(py::init<>())
+      .def(
+          "link_rtl",
+          [](PyIREEPyDMLoweringOptions &self,
+             PyIREEPyDMSourceBundle &sourceBundle) {
+            ireePyDMLoweringOptionsLinkRtl(self.wrapped, sourceBundle.wrapped);
+          },
+          "Enables linking against a runtime-library module");
+
   iree_pydm_m.def(
       "register_dialect",
       [](MlirContext context, bool load) {
@@ -76,12 +137,13 @@
 
   iree_pydm_m.def(
       "build_lower_to_iree_pass_pipeline",
-      [](MlirPassManager passManager) {
+      [](MlirPassManager passManager, PyIREEPyDMLoweringOptions &options) {
         MlirOpPassManager opPassManager =
             mlirPassManagerGetAsOpPassManager(passManager);
-        mlirIREEPyDMBuildLowerToIREEPassPipeline(opPassManager);
+        mlirIREEPyDMBuildLowerToIREEPassPipeline(opPassManager,
+                                                 options.wrapped);
       },
-      py::arg("pass_manager"));
+      py::arg("pass_manager"), py::arg("link_rtl_asm") = py::none());
 
 #define DEFINE_IREEPYDM_NULLARY_TYPE(Name)                                 \
   mlir_type_subclass(iree_pydm_m, #Name "Type", mlirTypeIsAIREEPyDM##Name, \
diff --git a/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/importer/util.py b/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/importer/util.py
index 8eb41b1..d8bfd1c 100644
--- a/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/importer/util.py
+++ b/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/importer/util.py
@@ -361,8 +361,10 @@
              f"implemented for this compiler")
 
 
-def create_context() -> ir.Context:
+def create_context(*, debug: bool = False) -> ir.Context:
   context = ir.Context()
+  if debug:
+    context.enable_multithreading(False)
   d.register_dialect(context)
   return context
 
diff --git a/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/rtl/__init__.py b/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/rtl/__init__.py
index 7bea5c5..1255fc0 100644
--- a/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/rtl/__init__.py
+++ b/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/rtl/__init__.py
@@ -3,3 +3,22 @@
 # Licensed under the Apache License v2.0 with LLVM Exceptions.
 # See https://llvm.org/LICENSE.txt for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+import functools as _functools
+
+from typing import Sequence
+from .base import RtlBuilder, RtlModule
+
+
+def _get_std_rtl_modules() -> Sequence[RtlModule]:
+  from .modules import (
+      booleans,
+      numerics,
+  )
+  return [m.RTL_MODULE for m in (booleans, numerics)]
+
+
+STD_RTL_MODULES = _get_std_rtl_modules()
+
+# Source bundle for the standard RTL.
+get_std_rtl_source_bundle = RtlBuilder.lazy_build_source_bundle(STD_RTL_MODULES)
diff --git a/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/rtl/base.py b/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/rtl/base.py
index 240317a..ae23206 100644
--- a/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/rtl/base.py
+++ b/llvm-external-projects/iree-dialects/python/iree/compiler/dialects/iree_pydm/rtl/base.py
@@ -5,9 +5,10 @@
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 """Base helpers for the RTL DSL."""
 
-from typing import List, Optional
+from typing import Callable, List, Optional, Sequence
 
 import functools
+import threading
 
 from ..importer import (
     create_context,
@@ -18,8 +19,15 @@
     ImportStage,
 )
 
-from ... import builtin as builtin_d
-from .... import (ir, passmanager, transforms as unused_transforms)
+from ... import (
+    builtin as builtin_d,
+    iree_pydm as pydm_d,
+)
+from .... import (
+    ir,
+    passmanager,
+    transforms as unused_transforms,
+)
 
 
 class RtlModule:
@@ -88,6 +96,36 @@
         ir.Location.unknown(context=self.context))
     self.module_op = self.root_module.operation
 
+  @staticmethod
+  def build_modules(rtl_modules: Sequence[RtlModule]) -> bytes:
+    """One shot build modules and return assembly."""
+    b = RtlBuilder()
+    b.emit_modules(rtl_modules)
+    b.optimize()
+    return b.root_module.operation.get_asm(binary=True, enable_debug_info=True)
+
+  @staticmethod
+  def lazy_build_source_bundle(
+      rtl_modules: Sequence[RtlModule]) -> Callable[[], pydm_d.SourceBundle]:
+    """Returns a function to lazily build RTL modules.
+
+    Modules will only be built once and cached for the life of the function.
+    Since RTL asm is typically passed unsafely to compiler passes, caching
+    forever is important.
+    """
+    rtl_modules = tuple(rtl_modules)
+    cache = []
+    lock = threading.Lock()
+
+    def get() -> pydm_d.SourceBundle:
+      with lock:
+        if not cache:
+          asm_blob = RtlBuilder.build_modules(rtl_modules)
+          cache.append(pydm_d.SourceBundle.from_asm(asm_blob))
+        return cache[0]
+
+    return get
+
   def emit_module(self, rtl_module: RtlModule):
     root_body = self.module_op.regions[0].blocks[0]
     with ir.InsertionPoint(root_body), ir.Location.unknown():
@@ -101,6 +139,10 @@
       # Getting the symbol implies exporting it into the module.
       f.get_or_create_provided_func_symbol(stage)
 
+  def emit_modules(self, rtl_modules: Sequence[RtlModule]):
+    for rtl_module in rtl_modules:
+      self.emit_module(rtl_module)
+
   def optimize(self):
     """Optimizes the RTL modules by running through stage 1 compilation."""
     with self.context:
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/constants.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/constants.mlir
new file mode 100644
index 0000000..d8fcb91
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/constants.mlir
@@ -0,0 +1,24 @@
+// RUN: iree-dialects-opt -split-input-file -convert-iree-pydm-to-iree %s | FileCheck --enable-var-scope --dump-input-filter=all %s
+
+// CHECK-LABEL: @none_constant
+iree_pydm.func @none_constant() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: %[[CST0:.*]] = constant 0 : i32
+  // CHECK: %[[CST1:.*]] = constant 0 : i32
+  // CHECK: return %[[CST1]], %[[CST0]]
+  %0 = none
+  return %0 : !iree_pydm.none
+}
+
+// CHECK-LABEL: @constant_integer_trunc
+iree_pydm.func @constant_integer_trunc() -> (!iree_pydm.exception_result, !iree_pydm.integer) {
+  // CHECK: constant -10 : i32
+  %0 = constant -10 : i64 -> !iree_pydm.integer
+  return %0 : !iree_pydm.integer
+}
+
+// CHECK-LABEL: @constant_real_trunc
+iree_pydm.func @constant_real_trunc() -> (!iree_pydm.exception_result, !iree_pydm.real) {
+  // CHECK: constant -2.000000e+00 : f32
+  %0 = constant -2.0 : f64 -> !iree_pydm.real
+  return %0 : !iree_pydm.real
+}
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/integer_compare.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/integer_compare.mlir
new file mode 100644
index 0000000..dfd387c
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/integer_compare.mlir
@@ -0,0 +1,65 @@
+// RUN: iree-dialects-opt -convert-iree-pydm-to-iree %s | FileCheck --enable-var-scope --dump-input-filter=all %s
+
+// CHECK-LABEL: @lt
+iree_pydm.func @lt(%arg0 : !iree_pydm.integer, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpi slt, %arg0, %arg1 : i32
+  %0 = apply_compare "lt", %arg0, %arg1 : !iree_pydm.integer, !iree_pydm.integer
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @le
+iree_pydm.func @le(%arg0 : !iree_pydm.integer, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpi sle, %arg0, %arg1 : i32
+  %0 = apply_compare "le", %arg0, %arg1 : !iree_pydm.integer, !iree_pydm.integer
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @eq
+iree_pydm.func @eq(%arg0 : !iree_pydm.integer, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpi eq, %arg0, %arg1 : i32
+  %0 = apply_compare "eq", %arg0, %arg1 : !iree_pydm.integer, !iree_pydm.integer
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @is
+iree_pydm.func @is(%arg0 : !iree_pydm.integer, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpi eq, %arg0, %arg1 : i32
+  %0 = apply_compare "is", %arg0, %arg1 : !iree_pydm.integer, !iree_pydm.integer
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @ne
+iree_pydm.func @ne(%arg0 : !iree_pydm.integer, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpi ne, %arg0, %arg1 : i32
+  %0 = apply_compare "ne", %arg0, %arg1 : !iree_pydm.integer, !iree_pydm.integer
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @isnot
+iree_pydm.func @isnot(%arg0 : !iree_pydm.integer, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpi ne, %arg0, %arg1 : i32
+  %0 = apply_compare "isnot", %arg0, %arg1 : !iree_pydm.integer, !iree_pydm.integer
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @gt
+iree_pydm.func @gt(%arg0 : !iree_pydm.integer, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpi sgt, %arg0, %arg1 : i32
+  %0 = apply_compare "gt", %arg0, %arg1 : !iree_pydm.integer, !iree_pydm.integer
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @ge
+iree_pydm.func @ge(%arg0 : !iree_pydm.integer, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpi sge, %arg0, %arg1 : i32
+  %0 = apply_compare "ge", %arg0, %arg1 : !iree_pydm.integer, !iree_pydm.integer
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/real_compare.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/real_compare.mlir
new file mode 100644
index 0000000..83601c1
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/real_compare.mlir
@@ -0,0 +1,65 @@
+// RUN: iree-dialects-opt -convert-iree-pydm-to-iree %s | FileCheck --enable-var-scope --dump-input-filter=all %s
+
+// CHECK-LABEL: @lt
+iree_pydm.func @lt(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpf olt, %arg0, %arg1 : f32
+  %0 = apply_compare "lt", %arg0, %arg1 : !iree_pydm.real, !iree_pydm.real
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @le
+iree_pydm.func @le(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpf ole, %arg0, %arg1 : f32
+  %0 = apply_compare "le", %arg0, %arg1 : !iree_pydm.real, !iree_pydm.real
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @eq
+iree_pydm.func @eq(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpf oeq, %arg0, %arg1 : f32
+  %0 = apply_compare "eq", %arg0, %arg1 : !iree_pydm.real, !iree_pydm.real
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @is
+iree_pydm.func @is(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpf oeq, %arg0, %arg1 : f32
+  %0 = apply_compare "is", %arg0, %arg1 : !iree_pydm.real, !iree_pydm.real
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @ne
+iree_pydm.func @ne(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpf one, %arg0, %arg1 : f32
+  %0 = apply_compare "ne", %arg0, %arg1 : !iree_pydm.real, !iree_pydm.real
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @isnot
+iree_pydm.func @isnot(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpf one, %arg0, %arg1 : f32
+  %0 = apply_compare "isnot", %arg0, %arg1 : !iree_pydm.real, !iree_pydm.real
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @gt
+iree_pydm.func @gt(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpf ogt, %arg0, %arg1 : f32
+  %0 = apply_compare "gt", %arg0, %arg1 : !iree_pydm.real, !iree_pydm.real
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
+
+// CHECK-LABEL: @ge
+iree_pydm.func @ge(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[R:.*]] = cmpf oge, %arg0, %arg1 : f32
+  %0 = apply_compare "ge", %arg0, %arg1 : !iree_pydm.real, !iree_pydm.real
+  // CHECK: return {{.*}}, %[[R]]
+  return %0 : !iree_pydm.bool
+}
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/structural.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/structural.mlir
index 35566e8..46cafc7 100644
--- a/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/structural.mlir
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/to_iree/structural.mlir
@@ -1,12 +1,27 @@
 // RUN: iree-dialects-opt -split-input-file -convert-iree-pydm-to-iree %s | FileCheck --enable-var-scope --dump-input-filter=all %s
 
-// CHECK-LABEL: @none_constant
-iree_pydm.func @none_constant() -> (!iree_pydm.exception_result, !iree_pydm.none) {
-  // CHECK: %[[CST0:.*]] = constant 0 : i32
-  // CHECK: %[[CST1:.*]] = constant 0 : i32
-  // CHECK: return %[[CST1]], %[[CST0]]
+// CHECK-LABEL: @bool_to_pred
+// NOTE: Also tests cond_br conversion.
+iree_pydm.func @bool_to_pred(%arg0 : !iree_pydm.bool) -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  %0 = bool_to_pred %arg0
+  %1 = none
+  // CHECK: cond_br %arg0
+  cond_br %0, ^bb1, ^bb2
+^bb1:
+  return %1 : !iree_pydm.none
+^bb2:
+  return %1 : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @br
+iree_pydm.func @br() -> (!iree_pydm.exception_result, !iree_pydm.none) {
   %0 = none
-  return %0 : !iree_pydm.none
+  // CHECK: br ^bb1({{.*}} : i32)
+  br ^bb1(%0 : !iree_pydm.none)
+  // CHECK: ^bb1(%0: i32):
+^bb1(%1 : !iree_pydm.none):
+  return %1 : !iree_pydm.none
 }
 
 // -----
@@ -109,3 +124,25 @@
   raise_on_failure %arg0 : !iree_pydm.exception_result
   return %arg1 : !iree_pydm.integer
 }
+
+// -----
+// CHECK-LABEL: @call_and_visibility
+iree_pydm.func @call_and_visibility(%arg0 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.integer) {
+  // CHECK: %[[R:.*]]:2 = call @callee(%arg0) : (i32) -> (i32, i32)
+  %0:2 = call @callee(%arg0) : (!iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.integer)
+  return %0#1 : !iree_pydm.integer
+}
+
+// CHECK: func private @callee
+iree_pydm.func private @callee(%arg0 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.integer) {
+  return %arg0 : !iree_pydm.integer
+}
+
+// -----
+// CHECK-LABEL: @get_type_code
+iree_pydm.func @get_type_code(%arg0 : !iree_pydm.object) -> (!iree_pydm.exception_result, !iree_pydm.integer) {
+  // CHECK: %[[c0:.*]] = constant 0 : index
+  // CHECK: %[[R:.*]] = iree.list.get %arg0[%[[c0]]] : !iree.list<!iree.variant> -> i32
+  %0 = get_type_code %arg0 : !iree_pydm.object
+  return %0 : !iree_pydm.integer
+}
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/rtl.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/rtl.py
new file mode 100644
index 0000000..9337da9
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/rtl.py
@@ -0,0 +1,6 @@
+# RUN: %PYTHON %s
+
+from iree.compiler.dialects.iree_pydm import rtl
+
+# Ensures that we can compile the standard library to a SourceModule.
+print(rtl.get_std_rtl_source_bundle())