Health and welfare on the iree_pydm dialect. (#6978)

Dialect changes:

* Simplify file naming to limit repetition.
* Add PythonTypeInterface, implemented by all Python types (currently used for some identity and numeric promotion rules).
* Apply previous recommendation and rework var alloc/load/store into a !free_var_ref type, alloc_free_var, load_var, store_var. Cell variables will need something different but this should generalize (i.e. cell vars need to be resolved symbolically, inter-procedurally).
* Add a UnionType in order to support type refinement (not yet used, and still needs some refinement).
* Forked scf.if into `functional_if` for the specific case where we are emitting conditional Python code of a functional nature (shows up in conditionals and short-circuit evals a lot, but most Python control flow is naturally CFG based). With this change, the pydm dialect is self-complete, not relying on ops from outside of itself. This will help with type inference, etc.
* Implemented OpAsmOpInterface::getDefaultDialect on `func` and `functional_if`, making all pydm ops able to be used prefix-free, cleaning up IR a lot.
* Made `none` ConstantLike. Added `success` and `failure` ops to produce `ExceptionResults`.
* Added `make_tuple` op (not yet used).
* Added `promote_numeric` op.
* Implemented simple folders for `constant`, `none`, `success`, `as_bool`, `bool_to_pred`, `raise_on_failure`, `select`.
* Implemented static numeric promotion with: a folder+canonicalizer on `promote_numeric`, a canonicalizer on `dynamic_binary_promote` which reduces it to primitives for static cases.
* Implemented no-op box/unbox canonicalizations.

With this, the dialect is in good shape to start building out the optimization and lowering pipelines.
diff --git a/llvm-external-projects/iree-dialects/BUILD b/llvm-external-projects/iree-dialects/BUILD
index 1531434..eb4db6e 100644
--- a/llvm-external-projects/iree-dialects/BUILD
+++ b/llvm-external-projects/iree-dialects/BUILD
@@ -92,31 +92,31 @@
     tbl_outs = [
         (
             ["-gen-dialect-decls"],
-            "include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsDialect.h.inc",
+            "include/iree-dialects/Dialect/IREEPyDM/IR/Dialect.h.inc",
         ),
         (
             ["-gen-dialect-defs"],
-            "include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsDialect.cpp.inc",
+            "include/iree-dialects/Dialect/IREEPyDM/IR/Dialect.cpp.inc",
         ),
         (
             ["-gen-op-decls"],
-            "include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.h.inc",
+            "include/iree-dialects/Dialect/IREEPyDM/IR/Ops.h.inc",
         ),
         (
             ["-gen-op-defs"],
-            "include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.cpp.inc",
+            "include/iree-dialects/Dialect/IREEPyDM/IR/Ops.cpp.inc",
         ),
         (
             ["-gen-typedef-decls"],
-            "include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsTypes.h.inc",
+            "include/iree-dialects/Dialect/IREEPyDM/IR/Types.h.inc",
         ),
         (
             ["-gen-typedef-defs"],
-            "include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsTypes.cpp.inc",
+            "include/iree-dialects/Dialect/IREEPyDM/IR/Types.cpp.inc",
         ),
     ],
     tblgen = "@llvm-project//mlir:mlir-tblgen",
-    td_file = "include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.td",
+    td_file = "include/iree-dialects/Dialect/IREEPyDM/IR/Ops.td",
     deps = [
         ":TdFiles",
         "@llvm-project//mlir:CallInterfacesTdFiles",
@@ -124,6 +124,26 @@
     ],
 )
 
+gentbl_cc_library(
+    name = "IREEPyDMInterfacesIncGen",
+    strip_include_prefix = "include",
+    tbl_outs = [
+        (
+            ["-gen-type-interface-decls"],
+            "include/iree-dialects/Dialect/IREEPyDM/IR/TypeInterfaces.h.inc",
+        ),
+        (
+            ["-gen-type-interface-defs"],
+            "include/iree-dialects/Dialect/IREEPyDM/IR/TypeInterfaces.cpp.inc",
+        ),
+    ],
+    tblgen = "@llvm-project//mlir:mlir-tblgen",
+    td_file = "include/iree-dialects/Dialect/IREEPyDM/IR/Interfaces.td",
+    deps = [
+        ":TdFiles",
+    ],
+)
+
 cc_library(
     name = "IREEPyDMDialect",
     srcs = glob([
@@ -132,6 +152,7 @@
     hdrs = glob(["include/iree-dialects/Dialect/IREEPyDM/IR/*.h"]),
     includes = ["include"],
     deps = [
+        ":IREEPyDMInterfacesIncGen",
         ":IREEPyDMOpsIncGen",
         "@llvm-project//llvm:Support",
         "@llvm-project//mlir:CallOpInterfaces",
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 ac17a04..27a1ba2 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
@@ -36,6 +36,7 @@
 IREEPYDM_DECLARE_NULLARY_TYPE(Bytes)
 IREEPYDM_DECLARE_NULLARY_TYPE(Integer)
 IREEPYDM_DECLARE_NULLARY_TYPE(ExceptionResult)
+IREEPYDM_DECLARE_NULLARY_TYPE(FreeVarRef)
 IREEPYDM_DECLARE_NULLARY_TYPE(List)
 IREEPYDM_DECLARE_NULLARY_TYPE(None)
 IREEPYDM_DECLARE_NULLARY_TYPE(Real)
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMBase.td b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Base.td
similarity index 92%
rename from llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMBase.td
rename to llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Base.td
index 40071d7..4ebc3de 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMBase.td
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Base.td
@@ -30,12 +30,13 @@
         optimization benefits compared to fully untyped programs.
   }];
   let cppNamespace = "::mlir::iree_pydm";
+  let hasConstantMaterializer = 1;
 }
 
 class IREEPyDM_Op<string mnemonic, list<OpTrait> traits = []> :
     Op<IREEPyDM_Dialect, mnemonic, traits>;
 class IREEPyDM_PureOp<string mnemonic, list<OpTrait> traits = []> :
     Op<IREEPyDM_Dialect, mnemonic, !listconcat(traits, [NoSideEffect])>;
-class IREEPyDM_TypeDef<string name> : TypeDef<IREEPyDM_Dialect, name>;
+class IREEPyDM_TypeDef<string name, list<Trait> traits = []> : TypeDef<IREEPyDM_Dialect, name, traits>;
 
 #endif // IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_BASE_TD
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/CMakeLists.txt b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/CMakeLists.txt
index 5d94ff9..be5a935 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/CMakeLists.txt
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/CMakeLists.txt
@@ -1,3 +1,22 @@
-add_mlir_dialect(IREEPyDMOps iree_pydm)
-add_mlir_doc(IREEPyDMDialect IREEPyDMDialect IREEPyDM/ -gen-dialect-doc)
-add_mlir_doc(IREEPyDMOps IREEPyDMOps IREEPyDM/ -gen-op-doc)
+function(_add_interfaces)
+  set(LLVM_TARGET_DEFINITIONS Interfaces.td)
+  mlir_tablegen(TypeInterfaces.h.inc -gen-type-interface-decls)
+  mlir_tablegen(TypeInterfaces.cpp.inc -gen-type-interface-defs)
+  add_public_tablegen_target(MLIRIREEPyDMInterfacesIncGen)
+endfunction()
+
+function(_add_dialect)
+  set(LLVM_TARGET_DEFINITIONS Ops.td)
+  mlir_tablegen(Ops.h.inc -gen-op-decls)
+  mlir_tablegen(Ops.cpp.inc -gen-op-defs)
+  mlir_tablegen(Types.h.inc -gen-typedef-decls)
+  mlir_tablegen(Types.cpp.inc -gen-typedef-defs)
+  mlir_tablegen(Dialect.h.inc -gen-dialect-decls -dialect=iree_pydm)
+  mlir_tablegen(Dialect.cpp.inc -gen-dialect-defs -dialect=iree_pydm)
+  add_public_tablegen_target(MLIRIREEPyDMOpsIncGen)
+  add_dependencies(MLIRIREEPyDMOpsIncGen MLIRIREEPyDMInterfacesIncGen)
+  add_dependencies(mlir-headers MLIRIREEPyDMOpsIncGen)
+endfunction()
+
+_add_dialect()
+_add_interfaces()
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.h b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Dialect.h
similarity index 70%
rename from llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.h
rename to llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Dialect.h
index fb714cb..642898a 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.h
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Dialect.h
@@ -7,12 +7,32 @@
 #ifndef IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_DIALECT_H
 #define IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_DIALECT_H
 
+#include "iree-dialects/Dialect/IREEPyDM/IR/Interfaces.h"
 #include "mlir/IR/Dialect.h"
 #include "mlir/IR/Types.h"
 
 namespace mlir {
 namespace iree_pydm {
 
+// Each built-in (to the compiler) type has a unique code, enumerated here.
+// Generally, the closed part of the type system will have type codes <
+// FirstCustom.
+enum class BuiltinTypeCode : int {
+  Bool = 1,
+  Bytes,
+  ExceptionResult,
+  Integer,
+  List,
+  None,
+  Object,
+  Real,
+  Str,
+  Tuple,
+  Type,
+
+  FirstCustom = 100,
+};
+
 /// Base class for all unboxed primitive types.
 class PrimitiveType : public mlir::Type {
  public:
@@ -25,10 +45,10 @@
 
 // Include generated dialect code (this comment blocks clang-format from
 // clobbering order).
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsDialect.h.inc"
+#include "iree-dialects/Dialect/IREEPyDM/IR/Dialect.h.inc"
 
 #define GET_TYPEDEF_CLASSES
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsTypes.h.inc"
+#include "iree-dialects/Dialect/IREEPyDM/IR/Types.h.inc"
 
 namespace mlir {
 namespace iree_pydm {
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.td b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Dialect.td
similarity index 70%
rename from llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.td
rename to llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Dialect.td
index 3bc6167..7cab375 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.td
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Dialect.td
@@ -7,7 +7,26 @@
 #ifndef IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_DIALECT_TD
 #define IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_DIALECT_TD
 
-include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMBase.td"
+include "iree-dialects/Dialect/IREEPyDM/IR/Base.td"
+include "iree-dialects/Dialect/IREEPyDM/IR/Interfaces.td"
+
+//===----------------------------------------------------------------------===//
+// Variable Types
+//===----------------------------------------------------------------------===//
+
+def IREEPyDM_FreeVarRef : IREEPyDM_TypeDef<"FreeVarRef"> {
+  let mnemonic = "free_var_ref";
+  let summary = "A direct access 'free' variable slot within a func";
+  let description = [{
+    A 'free variable' is a non-shared, direct access variable in a function.
+    Other languages would call these 'locals'. In Python, they are distinguished
+    from 'cell variables'.
+  }];
+}
+
+def IREEPyDM_AnyVarRef : AnyTypeOf<[
+  IREEPyDM_FreeVarRef,
+], "Python variable reference">;
 
 //===----------------------------------------------------------------------===//
 // Unboxed Primitive Types
@@ -16,8 +35,11 @@
 // Declare a new primitive type.
 // When adding a new one, update the PrimitiveType::classof method in
 // IREEPyDMDialect.h.
-class IREEPyDM_PrimitiveTypeDef<string name> :
-    TypeDef<IREEPyDM_Dialect, name, /*traits=*/[],
+class IREEPyDM_PrimitiveTypeDef<
+  string name, list<string> overridenMethods = []> :
+    TypeDef<IREEPyDM_Dialect, name, /*traits=*/[
+      DeclareTypeInterfaceMethods<PythonTypeInterface, overridenMethods>
+    ],
     /*baseCppClass=*/"::mlir::iree_pydm::PrimitiveType"> {
 }
 
@@ -26,7 +48,7 @@
     "unboxed primitive type",
     "::mlir::iree_pydm::PrimitiveType">;
 
-def IREEPyDM_BoolType : IREEPyDM_PrimitiveTypeDef<"Bool"> {
+def IREEPyDM_BoolType : IREEPyDM_PrimitiveTypeDef<"Bool", ["getNumericPromotionOrder"]> {
   let mnemonic = "bool";
 
   let summary = "Type of bool values";
@@ -62,7 +84,7 @@
   }];
 }
 
-def IREEPyDM_IntegerType : IREEPyDM_PrimitiveTypeDef<"Integer"> {
+def IREEPyDM_IntegerType : IREEPyDM_PrimitiveTypeDef<"Integer", ["getNumericPromotionOrder"]> {
   let mnemonic = "integer";
 
   let summary = "Type of integer values";
@@ -95,7 +117,7 @@
   }];
 }
 
-def IREEPyDM_RealType : IREEPyDM_PrimitiveTypeDef<"Real"> {
+def IREEPyDM_RealType : IREEPyDM_PrimitiveTypeDef<"Real", ["getNumericPromotionOrder"]> {
   let mnemonic = "real";
 
   let summary = "Type of floating point values";
@@ -143,7 +165,8 @@
 // Boxed objects
 //===----------------------------------------------------------------------===//
 
-def IREEPyDM_ObjectType : IREEPyDM_TypeDef<"Object"> {
+def IREEPyDM_ObjectType : IREEPyDM_TypeDef<
+    "Object", [DeclareTypeInterfaceMethods<PythonTypeInterface>]> {
   let mnemonic = "object";
 
   let summary = "Core data type having an identity, type and value";
@@ -198,6 +221,52 @@
 }
 
 //===----------------------------------------------------------------------===//
+// Union type
+//===----------------------------------------------------------------------===//
+
+def IREEPyDM_UnionType : IREEPyDM_TypeDef<"Union"> {
+  let mnemonic = "union";
+
+  let summary = "Represents a union of types";
+
+  let description = [{
+    Unions show up naturally in Python data-flows and this type represents
+    them. Note that it is not a "real" type in that it has not runtime
+    realization. However, most ops can be parameterized in terms of unions, and
+    it is generally the job of the compiler to do something sensible with them
+    before lowering.
+  }];
+
+  let parameters = (ins
+    ArrayRefParameter<"::mlir::Type">:$alternatives
+  );
+
+  let genVerifyDecl = 1;
+  let printer = [{
+    $_printer << getMnemonic();
+    llvm::interleaveComma(getAlternatives(), $_printer);
+  }];
+
+  let parser = [{
+    if (parser.parseOptionalLess())
+      return get($_ctxt, {});
+
+    SmallVector<::mlir::Type> alternatives;
+
+    do {
+      Type type;
+      if ($_parser.parseType(type))
+        return Type();
+      alternatives.push_back(type);
+    } while (succeeded($_parser.parseOptionalComma()));
+
+    return getChecked([&]() {
+      return $_parser.emitError($_parser.getNameLoc());
+    }, $_ctxt, alternatives);
+  }];
+}
+
+//===----------------------------------------------------------------------===//
 // Predicates and aggregate definitions
 //===----------------------------------------------------------------------===//
 
@@ -210,6 +279,12 @@
   IREEPyDM_PrimitiveType,
 ], "Python boxed or unboxed value">;
 
+def IREEPyDM_AnyNumericType : AnyTypeOf<[
+  IREEPyDM_BoolType,
+  IREEPyDM_IntegerType,
+  IREEPyDM_RealType,
+], "Python numeric type">;
+
 def IREEPyDM_GenericObjectType : Type<
     CPred<"::mlir::iree_pydm::ObjectType::isGenericObjectType($_self)">,
     "generic object",
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Interfaces.h b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Interfaces.h
new file mode 100644
index 0000000..0dd249b
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Interfaces.h
@@ -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
+
+#ifndef IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_INTERFACES_H
+#define IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_INTERFACES_H
+
+#include "mlir/IR/Types.h"
+
+namespace mlir {
+namespace iree_pydm {
+
+enum class BuiltinTypeCode;
+
+}  // namespace iree_pydm
+}  // namespace mlir
+
+#include "iree-dialects/Dialect/IREEPyDM/IR/TypeInterfaces.h.inc"
+
+#endif  // IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_INTERFACES_H
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Interfaces.td b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Interfaces.td
new file mode 100644
index 0000000..804ded8
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Interfaces.td
@@ -0,0 +1,42 @@
+// 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
+
+#ifndef IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_INTERFACES_TD
+#define IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_INTERFACES_TD
+
+include "mlir/IR/OpBase.td"
+
+def PythonTypeInterface : TypeInterface<"PythonTypeInterface"> {
+  let description = [{
+    Interface implemented by all Python types which represent a value in the
+    data model.
+  }];
+  let cppNamespace = "::mlir::iree_pydm";
+
+  let methods = [
+    InterfaceMethod<[{
+      Gets the type code.
+    }], "BuiltinTypeCode", "getTypeCode", (ins)>,
+
+    InterfaceMethod<[{
+      Gets a Python-relevant type name for this type.
+
+      This is used for both diagnostic messages and sorting order. No two
+      MLIR types can have the same name.
+    }], "llvm::StringRef", "getPythonTypeName", (ins)>,
+
+    InterfaceMethod<[{
+      For numeric types, returns the promotion order.
+      Types with a lower promotion order will be promoted to the higher order
+      for most binary functions.
+    }], "llvm::Optional<int>", "getNumericPromotionOrder", (ins),
+    /*methodBody=*/[{}], /*defaultImplementation=*/[{
+      return {};
+    }]>
+  ];
+}
+
+#endif // IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_INTERFACES_TD
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.h b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Ops.h
similarity index 83%
rename from llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.h
rename to llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Ops.h
index d08b0d2..487cedc 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.h
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Ops.h
@@ -7,17 +7,18 @@
 #ifndef IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_IREEPYDM_IR_OPS_H
 #define IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_IREEPYDM_IR_OPS_H
 
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.h"
+#include "iree-dialects/Dialect/IREEPyDM/IR/Dialect.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/BuiltinTypes.h"
 #include "mlir/IR/Dialect.h"
 #include "mlir/IR/OpDefinition.h"
 #include "mlir/IR/OpImplementation.h"
+#include "mlir/IR/PatternMatch.h"
 #include "mlir/IR/SymbolTable.h"
 #include "mlir/Interfaces/ControlFlowInterfaces.h"
 #include "mlir/Interfaces/SideEffectInterfaces.h"
 
 #define GET_OP_CLASSES
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.h.inc"
+#include "iree-dialects/Dialect/IREEPyDM/IR/Ops.h.inc"
 
 #endif  // IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_IREEPYDM_IR_OPS_H
diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.td b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Ops.td
similarity index 76%
rename from llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.td
rename to llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Ops.td
index 2a887f9..bc961ff 100644
--- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.td
+++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/IREEPyDM/IR/Ops.td
@@ -7,13 +7,62 @@
 #ifndef IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_OPS_TD
 #define IREE_LLVM_EXTERNAL_PROJECTS_IREE_DIALECTS_DIALECT_IREEPYDM_IR_OPS_TD
 
-include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.td"
+include "iree-dialects/Dialect/IREEPyDM/IR/Dialect.td"
 include "mlir/Interfaces/SideEffectInterfaces.td"
 include "mlir/Interfaces/CallInterfaces.td"
 include "mlir/Interfaces/ControlFlowInterfaces.td"
+include "mlir/IR/OpAsmInterface.td"
 include "mlir/IR/SymbolInterfaces.td"
 
 //===----------------------------------------------------------------------===//
+// Variable access
+//===----------------------------------------------------------------------===//
+
+def IREEPyDM_AllocFreeVarOp : IREEPyDM_PureOp<"alloc_free_var", [
+    MemoryEffects<[MemAlloc]>,
+    DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]> {
+  let summary = "Declares a 'free variable' by name and instance number";
+  let description = [{
+    Free variables represent allocated storage for a single value. By default
+    it is a variant which can store anything.
+  }];
+
+  let arguments = (ins
+    StrAttr:$name,
+    OptionalAttr<IndexAttr>:$index);
+  let results = (outs IREEPyDM_FreeVarRef:$free_var);
+  let assemblyFormat = [{
+    $name (`[` $index^ `]`)? `->` type($free_var) attr-dict
+  }];
+}
+
+def IREEPyDM_LoadVarOp : IREEPyDM_PureOp<"load_var", [MemoryEffects<[MemRead]>]> {
+  let summary = "Loads a value from a variable";
+  let description = [{
+    Loads a value from a variables. The value is implicitly cast to the given
+    result type, which must be legal.
+  }];
+
+  let arguments = (ins IREEPyDM_AnyVarRef:$var);
+  let results = (outs IREEPyDM_AnyValueType);
+
+  let assemblyFormat = [{
+    $var `:` type($var) `->` type(results) attr-dict
+  }];
+}
+
+def IREEPyDM_StoreVarOp : IREEPyDM_Op<"store_var", [MemoryEffects<[MemWrite]>]> {
+  let summary = "Stores a value to a variable";
+
+  let arguments = (ins
+    IREEPyDM_AnyVarRef:$var,
+    IREEPyDM_AnyValueType:$value);
+  let assemblyFormat = [{
+    $var `=` $value `:` type(operands) attr-dict
+  }];
+}
+
+//===----------------------------------------------------------------------===//
 // Functions
 //===----------------------------------------------------------------------===//
 
@@ -23,7 +72,8 @@
     IsolatedFromAbove,
     FunctionLike,
     CallableOpInterface,
-    Symbol]> {
+    Symbol,
+    DeclareOpInterfaceMethods<OpAsmOpInterface, ["getDefaultDialect"]>]> {
   let summary = "Python func";
   let description = [{
     Python functions map arguments to results and have the following additional
@@ -45,9 +95,9 @@
 
   let arguments = (ins SymbolNameAttr:$sym_name,
                        TypeAttr:$type,
-                       StrArrayAttr:$arg_names,
-                       StrArrayAttr:$free_vars,
-                       StrArrayAttr:$cell_vars,
+                       OptionalAttr<StrArrayAttr>:$arg_names,
+                       OptionalAttr<StrArrayAttr>:$free_vars,
+                       OptionalAttr<StrArrayAttr>:$cell_vars,
                        OptionalAttr<StrAttr>:$sym_visibility);
   let regions = (region AnyRegion:$body);
 
@@ -126,6 +176,8 @@
   let assemblyFormat = [{
     $exc_result `:` type($exc_result) attr-dict
   }];
+
+  let hasFolder = 1;
 }
 
 def IREEPyDM_CallOp : IREEPyDM_Op<"call", [
@@ -238,10 +290,13 @@
     reference primitives, the providence must be tracked and the original boxed
     value used (vs boxing a new one). Failure to do so will result in aliased
     objects.
+
+    Note that this operation allows to box object->object but canonicalizes
+    away in such a case (this is a convenience for IR construction).
   }];
 
   let arguments = (ins
-    IREEPyDM_AnyPrimitiveType:$primitive
+    IREEPyDM_AnyValueType:$primitive
   );
   let results = (outs
     IREEPyDM_ObjectType:$object
@@ -250,6 +305,8 @@
   let assemblyFormat = [{
     $primitive `:` type($primitive)  `->` type($object) attr-dict
   }];
+
+  let hasCanonicalizeMethod = 1;
 }
 
 def IREEPyDM_UnboxOp : IREEPyDM_PureOp<"unbox"> {
@@ -270,87 +327,8 @@
   let assemblyFormat = [{
     $object `:` type($object) `->` type($primitive) attr-dict
   }];
-}
 
-//===----------------------------------------------------------------------===//
-// Name access
-// While the CPython nomenclature is a bit inconsistent, we follow it where
-// it makes sense. Names are split into a few categories:
-//   - Free Variables: Local variables that are not visible outside of the
-//     containing (actual, not lexical) function. CPython bytecode refers
-//     to operations on these as "FAST" (i.e. LOAD_FAST, STORE_FAST, DEL_FAST).
-//   - Cell Variables: Variables that exist as part of the function closure
-//     and are resolved through a level of indirection (cell).
-//   - Global Variables: Variables that are resolved via the function's
-//     globals().
-// At this level, all operations are done by name (at the CPython instruction
-// level, it is by ordinal).
-//===----------------------------------------------------------------------===//
-
-// TODO: Consider adding a dedicated free_var op to bind free variables and
-// move the string attribute there.
-def IREEPyDM_LoadFreeVarOp : IREEPyDM_PureOp<"load_free_var"> {
-  let summary = "Loads a boxed free variable";
-  let description = [{
-    Loads the boxed object for a free variable. This is not failable at
-    runtime, and if the slot is not initialized, it will contain a special
-    NotInitialized primitive which cannot convert to anything else.
-
-    When importing Python programs, they must use boxed accessors to free
-    variable storage. Further analysis and transformation can promote these
-    to unboxed access.
-  }];
-
-  let arguments = (ins StrAttr:$name);
-  let results = (outs IREEPyDM_ObjectType:$value);
-
-  let assemblyFormat = [{
-    $name `->` type($value) attr-dict
-  }];
-}
-
-def IREEPyDM_StoreFreeVarOp : IREEPyDM_Op<"store_free_var"> {
-  let summary = "Stores a boxed free variable";
-  let description = [{
-    Stores a boxed value to a free variable slot.
-  }];
-
-  let arguments = (ins StrAttr:$name, IREEPyDM_ObjectType:$value);
-
-  let assemblyFormat = [{
-    $name `,` $value `:` type($value) attr-dict
-  }];
-}
-
-def IREEPyDM_LoadFreeVarUnboxedOp : IREEPyDM_PureOp<"load_free_var_unboxed"> {
-  let summary = "Loads an unboxed free variable";
-  let description = [{
-    Loads an unboxed value from a free variable slot. This will not be
-    emitted in regular programs but can be used in intrinsics and by
-    optimizations to remove boxing when types are guaranteed. It is a
-    program error to load a mismatched type and will produce undefined
-    behavior.
-  }];
-
-  let arguments = (ins StrAttr:$name);
-  let results = (outs IREEPyDM_PrimitiveType:$value);
-
-  let assemblyFormat = [{
-    $name `->` type($value) attr-dict
-  }];
-}
-
-def IREEPyDM_StoreFreeVarUnboxedOp : IREEPyDM_Op<"store_free_var_unboxed"> {
-  let summary = "Stores an unboxed free variable";
-  let description = [{
-    Stores an unboxed value to a free variable slot.
-  }];
-
-  let arguments = (ins StrAttr:$name, IREEPyDM_PrimitiveType:$value);
-
-  let assemblyFormat = [{
-    $name `,` $value `:` type($value) attr-dict
-  }];
+  let hasCanonicalizeMethod = 1;
 }
 
 //===----------------------------------------------------------------------===//
@@ -363,9 +341,9 @@
     This op supports immutable value types that have direct coding as MLIR
     attributes:
       IntType -> IntegerAttr<i64>
-      FloatType -> FloatAttr<double>
+      RealType -> FloatAttr<double>
       StrType -> StringAttr
-      BytesType -> BytesAttr
+      BytesType -> StringAttr
       BoolType -> IntegerAttr<i1>
   }];
 
@@ -379,14 +357,37 @@
   let extraClassDeclaration = [{
     Attribute getValue() { return (*this)->getAttr("value"); }
   }];
+  let hasFolder = 1;
 }
 
-def IREEPyDM_NoneOp : IREEPyDM_PureOp<"none"> {
+def IREEPyDM_NoneOp : IREEPyDM_PureOp<"none", [ConstantLike]> {
   let summary = "Gets the singleton NoneType primitive value";
   let results = (outs IREEPyDM_NoneType:$value);
   let assemblyFormat = [{
-    `->` type($value) attr-dict
+    attr-dict
   }];
+  let hasFolder = 1;
+}
+
+// TODO: Make ConstantLike
+def IREEPyDM_FailureOp : IREEPyDM_PureOp<"failure", []> {
+  let summary = "Generates a constant failure ExceptionResult";
+  let results = (outs IREEPyDM_ExceptionResultType);
+  let assemblyFormat = [{
+    `->` type(results) attr-dict
+  }];
+}
+
+def IREEPyDM_SuccessOp : IREEPyDM_PureOp<"success", [ConstantLike]> {
+  let summary = "Generates a constant success ExceptionResult";
+  let description = [{
+    A successful ExceptionResultType folds to a UnitAttr.
+  }];
+  let results = (outs IREEPyDM_ExceptionResultType);
+  let assemblyFormat = [{
+    `->` type(results) attr-dict
+  }];
+  let hasFolder = 1;
 }
 
 //===----------------------------------------------------------------------===//
@@ -400,6 +401,8 @@
   let assemblyFormat = [{
     $value `:` type($value) `->` type(results) attr-dict
   }];
+  let hasCanonicalizer = 1;
+  let hasFolder = 1;
 }
 
 def IREEPyDM_BoolToPredOp : IREEPyDM_PureOp<"bool_to_pred"> {
@@ -411,7 +414,20 @@
   let arguments = (ins IREEPyDM_BoolType:$value);
   let results = (outs I1);
   let assemblyFormat = [{
-    $value `:` type($value) `->` type(results) attr-dict
+    $value attr-dict
+  }];
+  let hasFolder = 1;
+}
+
+def IREEPyDM_MakeTupleOp : IREEPyDM_PureOp<"make_tuple"> {
+  let summary = "Makes a tuple from a static list of values";
+  let description = [{
+    Used for static construction of a tuple when the exact nature is known.
+  }];
+  let arguments = (ins Variadic<IREEPyDM_AnyValueType>:$slots);
+  let results = (outs IREEPyDM_TupleType:$tuple);
+  let assemblyFormat = [{
+    $slots `:` type($slots) `->` type(results) attr-dict
   }];
 }
 
@@ -431,6 +447,7 @@
   let assemblyFormat = [{
     $condition `,` $true_value `,` $false_value `:` type($result) attr-dict
   }];
+  let hasFolder = 1;
 }
 
 def IREEPyDM_ExprStatementDiscardOp : IREEPyDM_Op<"expr_statement_discard"> {
@@ -445,6 +462,34 @@
   }];
 }
 
+def FunctionalIfOp : IREEPyDM_Op<"functional_if", [
+    DeclareOpInterfaceMethods<RegionBranchOpInterface>,
+    SingleBlockImplicitTerminator<"iree_pydm::YieldOp">, RecursiveSideEffects,
+    NoRegionArguments,
+    DeclareOpInterfaceMethods<OpAsmOpInterface, ["getDefaultDialect"]>]> {
+  let summary = "A functional if construct";
+  let description = [{
+    This is similar to `scf.if` but adapted for this dialect. It has a required
+    then region and an optional else region. Types yielded from both must
+    match the result types of the `functional_if` op.
+  }];
+  let arguments = (ins IREEPyDM_BoolType:$condition);
+  let results = (outs Variadic<AnyType>:$results);
+  let regions = (region SizedRegion<1>:$thenRegion, AnyRegion:$elseRegion);
+
+  let printer = [{ return ::print(p, *this); }];
+  let verifier = [{ return ::verify(*this); }];
+  let parser = [{ return ::parse$cppClass(parser, result); }];
+}
+
+def YieldOp : IREEPyDM_Op<"yield", [NoSideEffect, ReturnLike, Terminator,
+                                    ParentOneOf<["FunctionalIfOp"]>]> {
+  let summary = "Yields a value from a functional control flow region";
+  let arguments = (ins Variadic<AnyType>:$results);
+  let assemblyFormat =
+      [{  attr-dict ($results^ `:` type($results))? }];
+}
+
 //===----------------------------------------------------------------------===//
 // Computation
 //===----------------------------------------------------------------------===//
@@ -477,6 +522,22 @@
   let assemblyFormat = [{
     $left `,` $right `:` type($left) `,` type($right) attr-dict
   }];
+  let hasCanonicalizeMethod = 1;
+}
+
+def IREEPyDM_PromoteNumericOp : IREEPyDM_PureOp<"promote_numeric"> {
+  let summary = "Promotes one numeric type to another higher on the hierarchy";
+  let description = [{
+    Given a numeric value of lower promotion order, promotes it to a higher
+    order.
+  }];
+  let arguments = (ins IREEPyDM_AnyNumericType:$input);
+  let results = (outs IREEPyDM_AnyNumericType);
+  let assemblyFormat = [{
+    $input `:` type(operands) `->` type(results) attr-dict
+  }];
+  let hasFolder = 1;
+  let hasCanonicalizeMethod = 1;
 }
 
 def IREEPyDM_ApplyBinaryOp : IREEPyDM_PureOp<"apply_binary"> {
diff --git a/llvm-external-projects/iree-dialects/lib/CAPI/Dialects.cpp b/llvm-external-projects/iree-dialects/lib/CAPI/Dialects.cpp
index 650a2c3..56b053a 100644
--- a/llvm-external-projects/iree-dialects/lib/CAPI/Dialects.cpp
+++ b/llvm-external-projects/iree-dialects/lib/CAPI/Dialects.cpp
@@ -7,7 +7,7 @@
 #include "iree-dialects-c/Dialects.h"
 
 #include "iree-dialects/Dialect/IREE/IREEDialect.h"
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.h"
+#include "iree-dialects/Dialect/IREEPyDM/IR/Dialect.h"
 #include "mlir/CAPI/Registration.h"
 
 //===----------------------------------------------------------------------===//
@@ -39,6 +39,7 @@
 IREEPYDM_DEFINE_NULLARY_TYPE(Bytes)
 IREEPYDM_DEFINE_NULLARY_TYPE(Integer)
 IREEPYDM_DEFINE_NULLARY_TYPE(ExceptionResult)
+IREEPYDM_DEFINE_NULLARY_TYPE(FreeVarRef)
 IREEPYDM_DEFINE_NULLARY_TYPE(List)
 IREEPYDM_DEFINE_NULLARY_TYPE(None)
 IREEPYDM_DEFINE_NULLARY_TYPE(Real)
@@ -51,6 +52,10 @@
 }
 
 MlirType mlirIREEPyDMObjectTypeGet(MlirContext ctx, MlirType primitive) {
+  if (!primitive.ptr) {
+    return wrap(mlir::iree_pydm::ObjectType::get(unwrap(ctx), nullptr));
+  }
+
   auto cppType = unwrap(primitive).cast<mlir::iree_pydm::PrimitiveType>();
   return wrap(mlir::iree_pydm::ObjectType::get(unwrap(ctx), cppType));
 }
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/CMakeLists.txt b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/CMakeLists.txt
index cb991b8..b6293fe 100644
--- a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/CMakeLists.txt
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/CMakeLists.txt
@@ -1,6 +1,6 @@
 add_mlir_library(IREEDialectsIREEPyDMDialect
-  IREEPyDMDialect.cpp
-  IREEPyDMOps.cpp
+  Dialect.cpp
+  Ops.cpp
 
   ADDITIONAL_HEADER_DIRS
   ${IREE_DIALECTS_SOURCE_DIR}/include
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/Dialect.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/Dialect.cpp
new file mode 100644
index 0000000..4b6870d
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/Dialect.cpp
@@ -0,0 +1,200 @@
+// 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/IR/Dialect.h"
+
+#include "iree-dialects/Dialect/IREEPyDM/IR/Interfaces.h"
+#include "iree-dialects/Dialect/IREEPyDM/IR/Ops.h"
+#include "llvm/ADT/TypeSwitch.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/DialectImplementation.h"
+#include "mlir/Support/LLVM.h"
+
+using namespace mlir;
+using namespace mlir::iree_pydm;
+
+#include "iree-dialects/Dialect/IREEPyDM/IR/Dialect.cpp.inc"
+
+#define GET_TYPEDEF_CLASSES
+#include "iree-dialects/Dialect/IREEPyDM/IR/TypeInterfaces.cpp.inc"
+#include "iree-dialects/Dialect/IREEPyDM/IR/Types.cpp.inc"
+
+//------------------------------------------------------------------------------
+// Dialect implementation
+//------------------------------------------------------------------------------
+
+using BuiltinIntegerType = mlir::IntegerType;
+
+using PyBoolType = mlir::iree_pydm::BoolType;
+using PyConstantOp = mlir::iree_pydm::ConstantOp;
+using PyIntegerType = mlir::iree_pydm::IntegerType;
+using PyRealType = mlir::iree_pydm::RealType;
+
+void IREEPyDMDialect::initialize() {
+  addTypes<
+#define GET_TYPEDEF_LIST
+#include "iree-dialects/Dialect/IREEPyDM/IR/Types.cpp.inc"
+      >();
+  addOperations<
+#define GET_OP_LIST
+#include "iree-dialects/Dialect/IREEPyDM/IR/Ops.cpp.inc"
+      >();
+}
+
+Operation *IREEPyDMDialect::materializeConstant(OpBuilder &builder,
+                                                Attribute value, Type type,
+                                                Location loc) {
+  // Since we support materialization of builtin types too, explicitly
+  // allow these.
+  if (type.isa<PyBoolType, BytesType, PyIntegerType, PyRealType, StrType,
+               BuiltinIntegerType>()) {
+    return builder.create<iree_pydm::ConstantOp>(loc, type, value);
+  }
+
+  if (type.isa<NoneType>()) {
+    return builder.create<iree_pydm::NoneOp>(loc, type);
+  }
+
+  if (type.isa<ExceptionResultType>() && value.isa<UnitAttr>()) {
+    return builder.create<iree_pydm::SuccessOp>(loc, type);
+  }
+
+  llvm_unreachable("unhandled iree_pydm constant materialization");
+}
+
+Type IREEPyDMDialect::parseType(DialectAsmParser &parser) const {
+  StringRef typeTag;
+  if (succeeded(parser.parseKeyword(&typeTag))) {
+    Type genType;
+    auto parseResult =
+        generatedTypeParser(getContext(), parser, typeTag, genType);
+    if (parseResult.hasValue()) {
+      if (*parseResult) {
+        return Type();
+      }
+      return genType;
+    }
+  }
+
+  parser.emitError(parser.getNameLoc(), "unknown dialect type");
+  return Type();
+}
+
+void IREEPyDMDialect::printType(Type type, DialectAsmPrinter &printer) const {
+  (void)generatedTypePrinter(type, printer);
+}
+
+//------------------------------------------------------------------------------
+// Python type implementation
+//------------------------------------------------------------------------------
+
+BuiltinTypeCode iree_pydm::BoolType::getTypeCode() const {
+  return BuiltinTypeCode::Bool;
+}
+
+StringRef iree_pydm::BoolType::getPythonTypeName() const { return "bool"; }
+
+Optional<int> iree_pydm::BoolType::getNumericPromotionOrder() const {
+  return 1;
+}
+
+BuiltinTypeCode iree_pydm::BytesType::getTypeCode() const {
+  return BuiltinTypeCode::Bytes;
+}
+
+StringRef iree_pydm::BytesType::getPythonTypeName() const { return "bytes"; }
+
+BuiltinTypeCode iree_pydm::ExceptionResultType::getTypeCode() const {
+  return BuiltinTypeCode::ExceptionResult;
+}
+
+StringRef iree_pydm::ExceptionResultType::getPythonTypeName() const {
+  return "Exception";
+}
+
+BuiltinTypeCode iree_pydm::IntegerType::getTypeCode() const {
+  return BuiltinTypeCode::Integer;
+}
+
+StringRef iree_pydm::IntegerType::getPythonTypeName() const { return "int"; }
+
+Optional<int> iree_pydm::IntegerType::getNumericPromotionOrder() const {
+  return 2;
+}
+
+BuiltinTypeCode iree_pydm::ListType::getTypeCode() const {
+  return BuiltinTypeCode::List;
+}
+
+StringRef iree_pydm::ListType::getPythonTypeName() const { return "list"; }
+
+BuiltinTypeCode iree_pydm::NoneType::getTypeCode() const {
+  return BuiltinTypeCode::None;
+}
+
+StringRef iree_pydm::NoneType::getPythonTypeName() const { return "None"; }
+
+BuiltinTypeCode iree_pydm::ObjectType::getTypeCode() const {
+  return BuiltinTypeCode::Object;
+}
+
+StringRef iree_pydm::ObjectType::getPythonTypeName() const { return "object"; }
+
+BuiltinTypeCode iree_pydm::RealType::getTypeCode() const {
+  return BuiltinTypeCode::Real;
+}
+
+StringRef iree_pydm::RealType::getPythonTypeName() const { return "float"; }
+
+Optional<int> iree_pydm::RealType::getNumericPromotionOrder() const {
+  return 3;
+}
+
+BuiltinTypeCode iree_pydm::StrType::getTypeCode() const {
+  return BuiltinTypeCode::Str;
+}
+
+StringRef iree_pydm::StrType::getPythonTypeName() const { return "str"; }
+
+BuiltinTypeCode iree_pydm::TupleType::getTypeCode() const {
+  return BuiltinTypeCode::Tuple;
+}
+
+StringRef iree_pydm::TupleType::getPythonTypeName() const { return "tuple"; }
+
+BuiltinTypeCode iree_pydm::TypeType::getTypeCode() const {
+  return BuiltinTypeCode::Type;
+}
+
+StringRef iree_pydm::TypeType::getPythonTypeName() const { return "type"; }
+
+//------------------------------------------------------------------------------
+// Union type implementation
+//------------------------------------------------------------------------------
+
+LogicalResult iree_pydm::UnionType::verify(
+    llvm::function_ref<InFlightDiagnostic()> emitError,
+    ArrayRef<Type> alternatives) {
+  int lastTypeCode = 0;
+  for (Type alternative : alternatives) {
+    if (auto pythonType =
+            alternative.dyn_cast<iree_pydm::PythonTypeInterface>()) {
+      int thisTypeCode = static_cast<int>(pythonType.getTypeCode());
+      // TODO: This doesn't account for parameterized types.
+      if (thisTypeCode <= lastTypeCode) {
+        return emitError() << "expected total order of union to be normative. "
+                              "got out of order: "
+                           << alternative;
+      }
+    } else {
+      return emitError() << "expected a python type in union. got: "
+                         << alternative;
+    }
+  }
+
+  return failure();
+}
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/IREEPyDMDialect.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/IREEPyDMDialect.cpp
deleted file mode 100644
index 50043ae..0000000
--- a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/IREEPyDMDialect.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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/IR/IREEPyDMDialect.h"
-
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.h"
-#include "llvm/ADT/TypeSwitch.h"
-#include "mlir/IR/DialectImplementation.h"
-#include "mlir/Support/LLVM.h"
-
-using namespace mlir;
-using namespace mlir::iree_pydm;
-
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsDialect.cpp.inc"
-
-#define GET_TYPEDEF_CLASSES
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsTypes.cpp.inc"
-
-void IREEPyDMDialect::initialize() {
-  addTypes<
-#define GET_TYPEDEF_LIST
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOpsTypes.cpp.inc"
-      >();
-  addOperations<
-#define GET_OP_LIST
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.cpp.inc"
-      >();
-}
-
-Type IREEPyDMDialect::parseType(DialectAsmParser &parser) const {
-  StringRef typeTag;
-  Type genType;
-  if (succeeded(parser.parseKeyword(&typeTag)))
-    generatedTypeParser(getContext(), parser, typeTag, genType);
-  return genType;
-}
-
-void IREEPyDMDialect::printType(Type type, DialectAsmPrinter &printer) const {
-  (void)generatedTypePrinter(type, printer);
-}
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/IREEPyDMOps.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/IREEPyDMOps.cpp
deleted file mode 100644
index 1bb1fa4..0000000
--- a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/IREEPyDMOps.cpp
+++ /dev/null
@@ -1,149 +0,0 @@
-// 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/IR/IREEPyDMOps.h"
-
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.h"
-#include "mlir/IR/Builders.h"
-#include "mlir/IR/BuiltinTypes.h"
-#include "mlir/IR/FunctionImplementation.h"
-#include "mlir/IR/OpImplementation.h"
-#include "mlir/IR/TypeUtilities.h"
-
-using namespace mlir;
-using namespace mlir::iree_pydm;
-
-using PyCallOp = mlir::iree_pydm::CallOp;
-using PyFuncOp = mlir::iree_pydm::FuncOp;
-
-//===----------------------------------------------------------------------===//
-// FuncOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult PyFuncOp::verifyType() {
-  // TODO: Enforce arg/result invariants.
-  return success();
-}
-
-static ParseResult parseFuncOp(OpAsmParser &parser, OperationState &result) {
-  auto buildFuncType = [](Builder &builder, ArrayRef<Type> argTypes,
-                          ArrayRef<Type> results,
-                          function_like_impl::VariadicFlag, std::string &) {
-    return builder.getFunctionType(argTypes, results);
-  };
-
-  return function_like_impl::parseFunctionLikeOp(
-      parser, result, /*allowVariadic=*/false, buildFuncType);
-}
-
-static void print(PyFuncOp op, OpAsmPrinter &p) {
-  FunctionType fnType = op.getType();
-  function_like_impl::printFunctionLikeOp(
-      p, op, fnType.getInputs(), /*isVariadic=*/false, fnType.getResults());
-}
-
-static LogicalResult verify(PyFuncOp op) {
-  // TODO: Enforce invariants.
-  return success();
-}
-
-//===----------------------------------------------------------------------===//
-// PatternMatchCallOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult PatternMatchCallOp::verifySymbolUses(
-    SymbolTableCollection &symbolTable) {
-  auto verifySymbols = [&](ArrayAttr symbols) -> LogicalResult {
-    for (auto symbolAttr : symbols) {
-      auto symbol = symbolAttr.cast<FlatSymbolRefAttr>();
-      PyFuncOp fn =
-          symbolTable.lookupNearestSymbolFrom<PyFuncOp>(*this, symbol);
-      if (!fn)
-        return emitOpError() << "'" << symbol.getValue()
-                             << "' does not reference a valid function";
-    }
-    return success();
-  };
-  auto genericsAttr = (*this)->getAttrOfType<ArrayAttr>("generic_match");
-  if (!genericsAttr)
-    return emitOpError(
-        "requires a 'generic_match' array of symbol reference attributes");
-  if (failed(verifySymbols(genericsAttr))) return failure();
-
-  auto specificsAttr = (*this)->getAttrOfType<ArrayAttr>("specific_match");
-  if (!specificsAttr)
-    return emitOpError(
-        "requires a 'specific_match' array of symbol reference attributes");
-  if (failed(verifySymbols(specificsAttr))) return failure();
-
-  return success();
-}
-
-//===----------------------------------------------------------------------===//
-// CallOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult PyCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
-  // Check that the callee attribute was specified.
-  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
-  if (!fnAttr)
-    return emitOpError("requires a 'callee' symbol reference attribute");
-  PyFuncOp fn = symbolTable.lookupNearestSymbolFrom<PyFuncOp>(*this, fnAttr);
-  if (!fn)
-    return emitOpError() << "'" << fnAttr.getValue()
-                         << "' does not reference a valid function";
-
-  // Verify that the operand and result types match the callee.
-  auto fnType = fn.getType();
-  if (fnType.getNumInputs() != getNumOperands())
-    return emitOpError("incorrect number of operands for callee");
-
-  for (unsigned i = 0, e = fnType.getNumInputs(); i != e; ++i) {
-    if (getOperand(i).getType() != fnType.getInput(i)) {
-      return emitOpError("operand type mismatch: expected operand type ")
-             << fnType.getInput(i) << ", but provided "
-             << getOperand(i).getType() << " for operand number " << i;
-    }
-  }
-
-  if (fnType.getNumResults() != getNumResults())
-    return emitOpError("incorrect number of results for callee");
-
-  for (unsigned i = 0, e = fnType.getNumResults(); i != e; ++i) {
-    if (getResult(i).getType() != fnType.getResult(i)) {
-      auto diag = emitOpError("result type mismatch at index ") << i;
-      diag.attachNote() << "      op result types: " << getResultTypes();
-      diag.attachNote() << "function result types: " << fnType.getResults();
-      return diag;
-    }
-  }
-
-  return success();
-}
-
-FunctionType PyCallOp::getCalleeType() {
-  return FunctionType::get(getContext(), getOperandTypes(), getResultTypes());
-}
-
-//===----------------------------------------------------------------------===//
-// DynamicCallOp
-//===----------------------------------------------------------------------===//
-
-LogicalResult DynamicCallOp::verifySymbolUses(
-    SymbolTableCollection &symbolTable) {
-  // Check that the callee attribute was specified.
-  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
-  if (!fnAttr)
-    return emitOpError("requires a 'callee' symbol reference attribute");
-  Operation *fn = symbolTable.lookupNearestSymbolFrom(*this, fnAttr);
-  if (!fn || !isa<PyFuncOp>(fn))
-    return emitOpError() << "'" << fnAttr.getValue()
-                         << "' does not reference a valid function";
-  return success();
-}
-
-#define GET_OP_CLASSES
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.cpp.inc"
diff --git a/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/Ops.cpp b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/Ops.cpp
new file mode 100644
index 0000000..245e6be
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/lib/Dialect/IREEPyDM/IR/Ops.cpp
@@ -0,0 +1,525 @@
+// 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/IR/Ops.h"
+
+#include "iree-dialects/Dialect/IREEPyDM/IR/Dialect.h"
+#include "llvm/ADT/TypeSwitch.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/FunctionImplementation.h"
+#include "mlir/IR/OpImplementation.h"
+#include "mlir/IR/TypeUtilities.h"
+
+using namespace mlir;
+using namespace mlir::iree_pydm;
+
+using PyBoolType = mlir::iree_pydm::BoolType;
+using PyConstantOp = mlir::iree_pydm::ConstantOp;
+using PyIntegerType = mlir::iree_pydm::IntegerType;
+using PyRealType = mlir::iree_pydm::RealType;
+using PyCallOp = mlir::iree_pydm::CallOp;
+using PyFuncOp = mlir::iree_pydm::FuncOp;
+
+//===----------------------------------------------------------------------===//
+// Utilities
+//===----------------------------------------------------------------------===//
+
+static Value getNumericZeroConstant(Location loc, Type numericType,
+                                    OpBuilder &builder) {
+  return TypeSwitch<Type, Value>(numericType)
+      .Case([&](PyBoolType t) -> Value {
+        return builder.create<PyConstantOp>(loc, t, builder.getBoolAttr(false));
+      })
+      .Case([&](PyIntegerType t) -> Value {
+        return builder.create<PyConstantOp>(loc, t,
+                                            builder.getI64IntegerAttr(0));
+      })
+      .Case([&](PyRealType t) -> Value {
+        return builder.create<PyConstantOp>(loc, t,
+                                            builder.getF64FloatAttr(0.0));
+      });
+}
+
+static Value getBoolConstant(Location loc, bool pred, OpBuilder &builder) {
+  return builder.create<PyConstantOp>(loc, builder.getType<BoolType>(),
+                                      builder.getBoolAttr(pred));
+}
+
+//===----------------------------------------------------------------------===//
+// Constants
+//===----------------------------------------------------------------------===//
+
+OpFoldResult PyConstantOp::fold(ArrayRef<Attribute> operands) {
+  assert(operands.empty() && "constant has no operands");
+  return getValue();
+}
+
+OpFoldResult NoneOp::fold(ArrayRef<Attribute> operands) {
+  assert(operands.empty() && "constant has no operands");
+  return UnitAttr::get(getContext());
+}
+
+OpFoldResult SuccessOp::fold(ArrayRef<Attribute> operands) {
+  assert(operands.empty() && "constant has no operands");
+  return UnitAttr::get(getContext());
+}
+
+//===----------------------------------------------------------------------===//
+// Variables
+//===----------------------------------------------------------------------===//
+
+void AllocFreeVarOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
+  setNameFn(getResult(), name());
+}
+
+//===----------------------------------------------------------------------===//
+// AsBoolOp
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct FoldAsBoolFromBool : public OpRewritePattern<AsBoolOp> {
+ public:
+  using OpRewritePattern::OpRewritePattern;
+  LogicalResult matchAndRewrite(AsBoolOp op,
+                                PatternRewriter &rewriter) const override {
+    if (op.value().getType().isa<BoolType>()) {
+      rewriter.replaceOp(op, op.value());
+      return success();
+    }
+    return failure();
+  }
+};
+
+struct FoldAsBoolFromNumeric : public OpRewritePattern<AsBoolOp> {
+ public:
+  using OpRewritePattern::OpRewritePattern;
+  LogicalResult matchAndRewrite(AsBoolOp op,
+                                PatternRewriter &rewriter) const override {
+    auto loc = op.getLoc();
+    auto ptType = op.value().getType().dyn_cast<PythonTypeInterface>();
+    if (!ptType) return failure();
+    if (!ptType.getNumericPromotionOrder()) return failure();
+
+    auto boolType = rewriter.getType<BoolType>();
+    Value zeroValue =
+        getNumericZeroConstant(loc, op.value().getType(), rewriter);
+    Value trueValue = getBoolConstant(loc, true, rewriter);
+    Value falseValue = getBoolConstant(loc, false, rewriter);
+    Value cmpResult = rewriter.create<ApplyCompareOp>(
+        loc, boolType, rewriter.getStringAttr("eq"), op.value(), zeroValue);
+    rewriter.replaceOpWithNewOp<SelectOp>(op, boolType, cmpResult, falseValue,
+                                          trueValue);
+    return success();
+  }
+};
+
+}  // namespace
+
+void AsBoolOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
+                                           MLIRContext *context) {
+  patterns.add<FoldAsBoolFromBool, FoldAsBoolFromNumeric>(context);
+}
+
+OpFoldResult AsBoolOp::fold(ArrayRef<Attribute> operands) {
+  Builder b(getContext());
+  // Fold NoneType to False.
+  if (value().getType().isa<NoneType>()) {
+    return b.getBoolAttr(false);
+  }
+
+  return {};
+}
+
+//===----------------------------------------------------------------------===//
+// BoolToPredOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult BoolToPredOp::fold(ArrayRef<Attribute> operands) {
+  if (!operands[0]) return {};
+  // Since both BoolType and I1 share the attribute form (an IntegerAttr of I1),
+  // we can just return it.
+  return operands[0];
+}
+
+//===----------------------------------------------------------------------===//
+// BoxOp and UnboxOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult BoxOp::canonicalize(BoxOp op, PatternRewriter &rewriter) {
+  // Sometimes boxes are emitted when the input is an object. Just remove.
+  if (op.primitive().getType().isa<ObjectType>()) {
+    rewriter.replaceOp(op, op.primitive());
+    return success();
+  }
+
+  return failure();
+}
+
+LogicalResult UnboxOp::canonicalize(UnboxOp unboxOp,
+                                    PatternRewriter &rewriter) {
+  auto loc = unboxOp.getLoc();
+
+  // Handle the case of an immediate BoxOp producer.
+  if (auto boxProducer =
+          dyn_cast_or_null<BoxOp>(unboxOp.object().getDefiningOp())) {
+    // If the producer is boxing to the same type we are unboxing, then
+    // just elide everything.
+    if (boxProducer.primitive().getType() == unboxOp.primitive().getType()) {
+      auto successValue = rewriter.create<SuccessOp>(
+          loc, rewriter.getType<ExceptionResultType>());
+      rewriter.replaceOp(unboxOp, {successValue, boxProducer.primitive()});
+      return success();
+    }
+  }
+  return failure();
+}
+
+//===----------------------------------------------------------------------===//
+// DynamicBinaryPromoteOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult DynamicBinaryPromoteOp::canonicalize(DynamicBinaryPromoteOp op,
+                                                   PatternRewriter &rewriter) {
+  auto loc = op.getLoc();
+  auto leftType = op.left().getType();
+  auto rightType = op.right().getType();
+  auto leftResultType = op.getResultTypes()[0];
+  auto rightResultType = op.getResultTypes()[1];
+  auto leftPt = leftType.dyn_cast<PythonTypeInterface>();
+  auto rightPt = rightType.dyn_cast<PythonTypeInterface>();
+  if (!leftPt || !rightPt) return failure();
+
+  Optional<int> leftOrder = leftPt.getNumericPromotionOrder();
+  Optional<int> rightOrder = rightPt.getNumericPromotionOrder();
+  Value newLeft = op.left();
+  Value newRight = op.right();
+
+  // Simple case: same types pass through.
+  if (leftType == rightType) {
+    // Nothing - pass-through rewrite.
+  } else if (leftOrder && rightOrder) {
+    // Both numeric.
+    if (*leftOrder > *rightOrder) {
+      newRight = rewriter.create<PromoteNumericOp>(loc, leftType, newRight);
+    }
+    if (*rightOrder > *leftOrder) {
+      newLeft = rewriter.create<PromoteNumericOp>(loc, rightType, newLeft);
+    }
+  } else {
+    return failure();
+  }
+
+  // Need to box back to the original type (which will always be a generic
+  // object).
+  newLeft = rewriter.create<BoxOp>(loc, leftResultType, newLeft);
+  newRight = rewriter.create<BoxOp>(loc, rightResultType, newRight);
+
+  rewriter.replaceOp(op, {newLeft, newRight});
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// FunctionalIfOp
+//===----------------------------------------------------------------------===//
+
+::llvm::StringRef FunctionalIfOp::getDefaultDialect() { return "iree_pydm"; }
+
+static LogicalResult verify(FunctionalIfOp op) {
+  if (op.getNumResults() != 0 && op.elseRegion().empty())
+    return op.emitOpError("must have an else block if defining values");
+
+  return RegionBranchOpInterface::verifyTypes(op);
+}
+
+static ParseResult parseFunctionalIfOp(OpAsmParser &parser,
+                                       OperationState &result) {
+  // Create the regions for 'then'.
+  result.regions.reserve(2);
+  Region *thenRegion = result.addRegion();
+  Region *elseRegion = result.addRegion();
+
+  auto &builder = parser.getBuilder();
+  OpAsmParser::OperandType cond;
+  Type conditionType = builder.getType<PyBoolType>();
+  if (parser.parseOperand(cond) ||
+      parser.resolveOperand(cond, conditionType, result.operands))
+    return failure();
+  // Parse optional results type list.
+  if (parser.parseOptionalArrowTypeList(result.types)) return failure();
+  // Parse the 'then' region.
+  if (parser.parseRegion(*thenRegion, /*arguments=*/{}, /*argTypes=*/{}))
+    return failure();
+  // IfOp::ensureTerminator(*thenRegion, parser.getBuilder(), result.location);
+
+  // If we find an 'else' keyword then parse the 'else' region.
+  if (!parser.parseOptionalKeyword("else")) {
+    if (parser.parseRegion(*elseRegion, /*arguments=*/{}, /*argTypes=*/{}))
+      return failure();
+    // IfOp::ensureTerminator(*elseRegion, parser.getBuilder(),
+    // result.location);
+  }
+
+  // Parse the optional attribute list.
+  if (parser.parseOptionalAttrDict(result.attributes)) return failure();
+  return success();
+}
+
+static void print(OpAsmPrinter &p, FunctionalIfOp op) {
+  bool printBlockTerminators = false;
+
+  p << " " << op.condition();
+  if (!op.results().empty()) {
+    p << " -> (" << op.getResultTypes() << ")";
+    // Print yield explicitly if the op defines values.
+    printBlockTerminators = true;
+  }
+  p.printRegion(op.thenRegion(),
+                /*printEntryBlockArgs=*/false,
+                /*printBlockTerminators=*/printBlockTerminators);
+
+  // Print the 'else' regions if it exists and has a block.
+  auto &elseRegion = op.elseRegion();
+  if (!elseRegion.empty()) {
+    p << " else";
+    p.printRegion(elseRegion,
+                  /*printEntryBlockArgs=*/false,
+                  /*printBlockTerminators=*/printBlockTerminators);
+  }
+
+  p.printOptionalAttrDict(op->getAttrs());
+}
+
+/// Given the region at `index`, or the parent operation if `index` is None,
+/// return the successor regions. These are the regions that may be selected
+/// during the flow of control. `operands` is a set of optional attributes that
+/// correspond to a constant value for each operand, or null if that operand is
+/// not a constant.
+void FunctionalIfOp::getSuccessorRegions(
+    Optional<unsigned> index, ArrayRef<Attribute> operands,
+    SmallVectorImpl<RegionSuccessor> &regions) {
+  // The `then` and the `else` region branch back to the parent operation.
+  if (index.hasValue()) {
+    regions.push_back(RegionSuccessor(getResults()));
+    return;
+  }
+
+  // Don't consider the else region if it is empty.
+  Region *elseRegion = &this->elseRegion();
+  if (elseRegion->empty()) elseRegion = nullptr;
+
+  // Otherwise, the successor is dependent on the condition.
+  if (auto condAttr = operands.front().dyn_cast_or_null<BoolAttr>()) {
+    bool condition = condAttr.getValue();
+    // Add the successor regions using the condition.
+    regions.push_back(RegionSuccessor(condition ? &thenRegion() : elseRegion));
+  } else {
+    // If the condition isn't constant, both regions may be executed.
+    regions.push_back(RegionSuccessor(&thenRegion()));
+    // If the else region does not exist, it is not a viable successor.
+    if (elseRegion) regions.push_back(RegionSuccessor(elseRegion));
+  }
+}
+
+//===----------------------------------------------------------------------===//
+// FuncOp
+//===----------------------------------------------------------------------===//
+
+::llvm::StringRef PyFuncOp::getDefaultDialect() { return "iree_pydm"; }
+
+LogicalResult PyFuncOp::verifyType() {
+  // TODO: Enforce arg/result invariants.
+  return success();
+}
+
+static ParseResult parseFuncOp(OpAsmParser &parser, OperationState &result) {
+  auto buildFuncType = [](Builder &builder, ArrayRef<Type> argTypes,
+                          ArrayRef<Type> results,
+                          function_like_impl::VariadicFlag, std::string &) {
+    return builder.getFunctionType(argTypes, results);
+  };
+
+  return function_like_impl::parseFunctionLikeOp(
+      parser, result, /*allowVariadic=*/false, buildFuncType);
+}
+
+static void print(PyFuncOp op, OpAsmPrinter &p) {
+  FunctionType fnType = op.getType();
+  function_like_impl::printFunctionLikeOp(
+      p, op, fnType.getInputs(), /*isVariadic=*/false, fnType.getResults());
+}
+
+static LogicalResult verify(PyFuncOp op) {
+  // TODO: Enforce invariants.
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// PatternMatchCallOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult PatternMatchCallOp::verifySymbolUses(
+    SymbolTableCollection &symbolTable) {
+  auto verifySymbols = [&](ArrayAttr symbols) -> LogicalResult {
+    for (auto symbolAttr : symbols) {
+      auto symbol = symbolAttr.cast<FlatSymbolRefAttr>();
+      PyFuncOp fn =
+          symbolTable.lookupNearestSymbolFrom<PyFuncOp>(*this, symbol);
+      if (!fn)
+        return emitOpError() << "'" << symbol.getValue()
+                             << "' does not reference a valid function";
+    }
+    return success();
+  };
+  auto genericsAttr = (*this)->getAttrOfType<ArrayAttr>("generic_match");
+  if (!genericsAttr)
+    return emitOpError(
+        "requires a 'generic_match' array of symbol reference attributes");
+  if (failed(verifySymbols(genericsAttr))) return failure();
+
+  auto specificsAttr = (*this)->getAttrOfType<ArrayAttr>("specific_match");
+  if (!specificsAttr)
+    return emitOpError(
+        "requires a 'specific_match' array of symbol reference attributes");
+  if (failed(verifySymbols(specificsAttr))) return failure();
+
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// PromoteNumericOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult PromoteNumericOp::fold(ArrayRef<Attribute> operands) {
+  if (!operands[0]) return {};
+
+  Builder b(getContext());
+  Attribute fromAttr = operands[0];
+  return TypeSwitch<Type, OpFoldResult>(getResult().getType())
+      .Case([&](PyIntegerType toType) -> OpFoldResult {
+        return TypeSwitch<Attribute, OpFoldResult>(fromAttr)
+            .Case([&](BoolAttr fromBool) -> OpFoldResult {
+              return b.getI64IntegerAttr(fromBool.getValue() ? 1 : 0);
+            })
+            .Default([](Attribute) -> OpFoldResult { return {}; });
+      })
+      .Case([&](PyRealType toType) -> OpFoldResult {
+        return TypeSwitch<Attribute, OpFoldResult>(fromAttr)
+            .Case([&](BoolAttr fromBool) -> OpFoldResult {
+              return b.getF64FloatAttr(fromBool.getValue() ? 1.0 : 0.0);
+            })
+            .Case([&](IntegerAttr fromInteger) -> OpFoldResult {
+              APInt value = fromInteger.getValue();
+              return b.getF64FloatAttr(value.getSExtValue());
+            })
+            .Default([](Attribute) -> OpFoldResult { return {}; });
+      })
+      .Default([](Type) -> OpFoldResult { return {}; });
+}
+
+LogicalResult PromoteNumericOp::canonicalize(PromoteNumericOp op,
+                                             PatternRewriter &rewriter) {
+  if (op.input().getType() == op.getResult().getType()) {
+    rewriter.replaceOp(op, op.input());
+    return success();
+  }
+  return failure();
+}
+
+//===----------------------------------------------------------------------===//
+// RaiseOnFailureOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult iree_pydm::RaiseOnFailureOp::fold(
+    ArrayRef<Attribute> operands, SmallVectorImpl<OpFoldResult> &results) {
+  assert(operands.size() == 1 && "expected one fold operand");
+  // Unit exception result is success. Just elide.
+  if (operands[0] && operands[0].isa<UnitAttr>()) {
+    erase();
+    return success();
+  }
+  return failure();
+}
+
+//===----------------------------------------------------------------------===//
+// SelectOp
+//===----------------------------------------------------------------------===//
+
+OpFoldResult SelectOp::fold(ArrayRef<Attribute> operands) {
+  if (!operands[0]) return {};
+
+  BoolAttr boolAttr = operands[0].cast<BoolAttr>();
+  if (boolAttr.getValue())
+    return true_value();
+  else
+    return false_value();
+}
+
+//===----------------------------------------------------------------------===//
+// CallOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult PyCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
+  // Check that the callee attribute was specified.
+  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
+  if (!fnAttr)
+    return emitOpError("requires a 'callee' symbol reference attribute");
+  PyFuncOp fn = symbolTable.lookupNearestSymbolFrom<PyFuncOp>(*this, fnAttr);
+  if (!fn)
+    return emitOpError() << "'" << fnAttr.getValue()
+                         << "' does not reference a valid function";
+
+  // Verify that the operand and result types match the callee.
+  auto fnType = fn.getType();
+  if (fnType.getNumInputs() != getNumOperands())
+    return emitOpError("incorrect number of operands for callee");
+
+  for (unsigned i = 0, e = fnType.getNumInputs(); i != e; ++i) {
+    if (getOperand(i).getType() != fnType.getInput(i)) {
+      return emitOpError("operand type mismatch: expected operand type ")
+             << fnType.getInput(i) << ", but provided "
+             << getOperand(i).getType() << " for operand number " << i;
+    }
+  }
+
+  if (fnType.getNumResults() != getNumResults())
+    return emitOpError("incorrect number of results for callee");
+
+  for (unsigned i = 0, e = fnType.getNumResults(); i != e; ++i) {
+    if (getResult(i).getType() != fnType.getResult(i)) {
+      auto diag = emitOpError("result type mismatch at index ") << i;
+      diag.attachNote() << "      op result types: " << getResultTypes();
+      diag.attachNote() << "function result types: " << fnType.getResults();
+      return diag;
+    }
+  }
+
+  return success();
+}
+
+FunctionType PyCallOp::getCalleeType() {
+  return FunctionType::get(getContext(), getOperandTypes(), getResultTypes());
+}
+
+//===----------------------------------------------------------------------===//
+// DynamicCallOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult DynamicCallOp::verifySymbolUses(
+    SymbolTableCollection &symbolTable) {
+  // Check that the callee attribute was specified.
+  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
+  if (!fnAttr)
+    return emitOpError("requires a 'callee' symbol reference attribute");
+  Operation *fn = symbolTable.lookupNearestSymbolFrom(*this, fnAttr);
+  if (!fn || !isa<PyFuncOp>(fn))
+    return emitOpError() << "'" << fnAttr.getValue()
+                         << "' does not reference a valid function";
+  return success();
+}
+
+#define GET_OP_CLASSES
+#include "iree-dialects/Dialect/IREEPyDM/IR/Ops.cpp.inc"
diff --git a/llvm-external-projects/iree-dialects/python/IREEDialectsModule.cpp b/llvm-external-projects/iree-dialects/python/IREEDialectsModule.cpp
index 8bf5864..00d280a 100644
--- a/llvm-external-projects/iree-dialects/python/IREEDialectsModule.cpp
+++ b/llvm-external-projects/iree-dialects/python/IREEDialectsModule.cpp
@@ -87,6 +87,7 @@
   DEFINE_IREEPYDM_NULLARY_TYPE(Bool)
   DEFINE_IREEPYDM_NULLARY_TYPE(Bytes)
   DEFINE_IREEPYDM_NULLARY_TYPE(ExceptionResult)
+  DEFINE_IREEPYDM_NULLARY_TYPE(FreeVarRef)
   DEFINE_IREEPYDM_NULLARY_TYPE(Integer)
   DEFINE_IREEPYDM_NULLARY_TYPE(List)
   DEFINE_IREEPYDM_NULLARY_TYPE(None)
diff --git a/llvm-external-projects/iree-dialects/python/mlir/dialects/IreePyDmBinding.td b/llvm-external-projects/iree-dialects/python/mlir/dialects/IreePyDmBinding.td
index 900c7d1..ff6371f 100644
--- a/llvm-external-projects/iree-dialects/python/mlir/dialects/IreePyDmBinding.td
+++ b/llvm-external-projects/iree-dialects/python/mlir/dialects/IreePyDmBinding.td
@@ -8,6 +8,6 @@
 #define PYTHON_BINDINGS_IREE_PYDM_OPS
 
 include "mlir/Bindings/Python/Attributes.td"
-include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMOps.td"
+include "iree-dialects/Dialect/IREEPyDM/IR/Ops.td"
 
 #endif // PYTHON_BINDINGS_IREE_PYDM_OPS
diff --git a/llvm-external-projects/iree-dialects/python/mlir/dialects/iree_pydm/importer/importer.py b/llvm-external-projects/iree-dialects/python/mlir/dialects/iree_pydm/importer/importer.py
index 9cae4d6..a52edbb 100644
--- a/llvm-external-projects/iree-dialects/python/mlir/dialects/iree_pydm/importer/importer.py
+++ b/llvm-external-projects/iree-dialects/python/mlir/dialects/iree_pydm/importer/importer.py
@@ -4,7 +4,7 @@
 # See https://llvm.org/LICENSE.txt for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-from typing import Optional, Sequence, Tuple, List, Union
+from typing import Dict, Optional, Sequence, Set, Tuple, List, Union
 
 import ast
 import inspect
@@ -99,6 +99,7 @@
                            host_closure_vars=inspect.getclosurevars(f))
     body_importer = FunctionDefBodyImporter(fctx)
     with ic.scoped_ip(ir.InsertionPoint(entry_block)):
+      body_importer.declare_variables()
       body_importer.import_body(fd_node)
     return f_op
 
@@ -118,6 +119,7 @@
       "free_vars",
       "cell_vars",
       "host_closure_vars",
+      "variable_value_map",
   ]
 
   def __init__(self,
@@ -134,13 +136,32 @@
 
     # Keep sets of free and cell var names so that we know what kinds of
     # loads to issue.
-    self.arg_names = set(
+    self.arg_names: Set[str] = set(
         [ir.StringAttr(attr).value for attr in self.f_op.arg_names])
-    self.free_vars = set(
+    self.free_vars: Set[str] = set(
         [ir.StringAttr(attr).value for attr in self.f_op.free_vars])
-    self.cell_vars = set(
+    self.cell_vars: Set[str] = set(
         [ir.StringAttr(attr).value for attr in self.f_op.cell_vars])
 
+    self.variable_value_map: Dict[str, ir.Value] = {}
+
+  def declare_free_var(self, name: str):
+    """Declares a free-variable SSA value for the given name."""
+    ic = self.ic
+    if name in self.variable_value_map:
+      ic.abort(f"attempt to duplicate declare variable {name}")
+    with ic.ip, ic.loc:
+      t = d.FreeVarRefType.get()
+      self.variable_value_map[name] = (d.AllocFreeVarOp(t,
+                                                        ir.StringAttr.get(name),
+                                                        index=None).result)
+
+  def find_variable(self, name: str) -> ir.Value:
+    try:
+      return self.variable_value_map[name]
+    except KeyError:
+      self.ic.abort(f"attempt to reference variable not declared: {name}")
+
   def update_loc(self, ast_node):
     self.ic.set_file_line_col(self.filename, ast_node.lineno,
                               ast_node.col_offset)
@@ -204,6 +225,11 @@
     self.successor_block = successor_block
     self._last_was_return = False
 
+  def declare_variables(self):
+    fctx = self.fctx
+    for name in fctx.free_vars:
+      fctx.declare_free_var(name)
+
   def import_body(self, ast_fd: ast.FunctionDef):
     ic = self.fctx.ic
     # Function prologue: Initialize arguments.
@@ -237,7 +263,7 @@
     arg_value = entry_block.arguments[index]
     arg_value = ic.box(arg_value)
     with ic.loc, ic.ip:
-      d.StoreFreeVarOp(ir.StringAttr.get(name), arg_value)
+      d.StoreVarOp(fctx.find_variable(name), arg_value)
 
   def visit_Pass(self, ast_node):
     pass
@@ -268,7 +294,7 @@
       boxed = ic.box(expr.get_immediate())
       with ic.loc, ic.ip:
         target_id = target.id  # pytype: disable=attribute-error
-        d.StoreFreeVarOp(ir.StringAttr.get(target_id), boxed)
+        d.StoreVarOp(fctx.find_variable(target_id), boxed)
 
   def visit_Expr(self, node: ast.Expr):
     fctx = self.fctx
@@ -397,9 +423,9 @@
     with ic.loc:
       if node.id in self.fctx.free_vars:
         self._set_result(
-            d.LoadFreeVarOp(d.ObjectType.get(),
-                            ir.StringAttr.get(node.id),
-                            ip=ic.ip).result)
+            d.LoadVarOp(d.ObjectType.get(),
+                        fctx.find_variable(node.id),
+                        ip=ic.ip).result)
         return
 
     # Fall-back to global resolution.
@@ -463,24 +489,23 @@
       if not next_nodes:
         return next_value
 
-      bool_value = d.AsBoolOp(d.BoolType.get(), next_value, ip=ic.ip).result
-      condition_value = d.BoolToPredOp(ir.IntegerType.get_signless(1),
-                                       bool_value,
-                                       ip=ic.ip).result
+      condition_value = d.AsBoolOp(d.BoolType.get(), next_value,
+                                   ip=ic.ip).result
       # TODO: See if we can re-organize this to not force boxing through the
       # if.
-      if_op, then_ip, else_ip = ic.scf_IfOp([d.ObjectType.get()],
-                                            condition_value, True)
+      if_op, then_ip, else_ip = ic.create_functional_if_op([d.ObjectType.get()],
+                                                           condition_value,
+                                                           True)
       # Short-circuit return case.
       with ic.scoped_ip(then_ip if return_first_true else else_ip):
         next_value_casted = ic.box(next_value)
-        ic.scf_YieldOp([next_value_casted])
+        d.YieldOp([next_value_casted], loc=ic.loc, ip=ic.ip)
 
       # Nested evaluate next case.
       with ic.scoped_ip(else_ip if return_first_true else then_ip):
         nested_value = emit_next(next_nodes)
         nested_value_casted = next_value_casted = ic.box(nested_value)
-        ic.scf_YieldOp([nested_value_casted])
+        d.YieldOp([nested_value_casted], loc=ic.loc, ip=ic.ip)
 
       return if_op.result
 
@@ -531,18 +556,17 @@
       # Emit if for short circuit eval.
       # Since this is an 'and', all else clauses yield a false value.
       with ic.ip, ic.loc:
-        compare_result_i1 = d.BoolToPredOp(ir.IntegerType.get_signless(1),
-                                           compare_result).result
-        if_op, then_ip, else_ip = ic.scf_IfOp([d.BoolType.get()],
-                                              compare_result_i1, True)
+        if_op, then_ip, else_ip = ic.create_functional_if_op([d.BoolType.get()],
+                                                             compare_result,
+                                                             True)
       # Build the else clause.
       with ic.scoped_ip(else_ip):
-        ic.scf_YieldOp([false_value])
+        d.YieldOp([false_value], loc=ic.loc, ip=ic.ip)
 
       # Build the then clause.
       with ic.scoped_ip(then_ip):
         nested_result = emit_next(right_value, comparisons)
-        ic.scf_YieldOp([nested_result])
+        d.YieldOp([nested_result], loc=ic.loc, ip=ic.ip)
 
       return if_op.result
 
@@ -606,30 +630,26 @@
     # Interpret as bool.
     test_bool = d.AsBoolOp(d.BoolType.get(), test_value, ip=ic.ip,
                            loc=ic.loc).result
-    test_pred = d.BoolToPredOp(ir.IntegerType.get_signless(1),
-                               test_bool,
-                               ip=ic.ip,
-                               loc=ic.loc).result
 
     # TODO: There is a hazard here if then and else refine to different
     # boxed types. Needs a derefine cast. Also we are boxing to type erased
     # types to satisfy scf.if verifier. Do something better.
-    if_op, then_ip, else_ip = ic.scf_IfOp([d.ObjectType.get(ic.context)],
-                                          test_pred, True)
+    if_op, then_ip, else_ip = ic.create_functional_if_op(
+        [d.ObjectType.get(ic.context)], test_bool, True)
     # Build the then clause
     with ic.scoped_ip(then_ip):
       # Evaluate the true clause within the if body.
       sub_expression = ExpressionImporter(fctx)
       sub_expression.visit(node.body)
       then_result = sub_expression.get_immediate()
-      ic.scf_YieldOp([ic.box(then_result, to_typed=False)])
+      d.YieldOp([ic.box(then_result, to_typed=False)], loc=ic.loc, ip=ic.ip)
 
     # Build the then clause.
     with ic.scoped_ip(else_ip):
       sub_expression = ExpressionImporter(fctx)
       sub_expression.visit(node.orelse)
       orelse_result = sub_expression.get_immediate()
-      ic.scf_YieldOp([ic.box(orelse_result, to_typed=False)])
+      d.YieldOp([ic.box(orelse_result, to_typed=False)], loc=ic.loc, ip=ic.ip)
 
     self._set_result(if_op.result)
 
diff --git a/llvm-external-projects/iree-dialects/python/mlir/dialects/iree_pydm/importer/util.py b/llvm-external-projects/iree-dialects/python/mlir/dialects/iree_pydm/importer/util.py
index 5fcdc5f..23f7934 100644
--- a/llvm-external-projects/iree-dialects/python/mlir/dialects/iree_pydm/importer/util.py
+++ b/llvm-external-projects/iree-dialects/python/mlir/dialects/iree_pydm/importer/util.py
@@ -137,7 +137,7 @@
       elif isinstance(value, int):
         return d.ConstantOp(
             d.IntegerType.get(),
-            ir.IntegerAttr.get(ir.IntegerType.get_signed(64), value)).result
+            ir.IntegerAttr.get(ir.IntegerType.get_signless(64), value)).result
       elif isinstance(value, float):
         return d.ConstantOp(d.RealType.get(),
                             ir.FloatAttr.get(ir.F64Type.get(), value)).result
@@ -148,13 +148,14 @@
     self.abort(
         f"unsupported Python constant value '{value}' (an {value.__class__}))")
 
-  # TODO: Map the SCF dialect properly upstream.
-  def scf_IfOp(self, results, condition: ir.Value, with_else_region: bool):
-    """Creates an SCF if op.
+  def create_functional_if_op(self, results, condition: ir.Value,
+                              with_else_region: bool):
+    """Sugar to create a `functional_if`.
     Returns:
       (if_op, then_ip, else_ip) if with_else_region, otherwise (if_op, then_ip)
     """
-    op = ir.Operation.create("scf.if",
+    # TODO: ODS for these style of ops with variable regions needs work.
+    op = ir.Operation.create("iree_pydm.functional_if",
                              results=results,
                              operands=[condition],
                              regions=2 if with_else_region else 1,
@@ -169,12 +170,6 @@
     else:
       return op, ir.InsertionPoint(then_block)
 
-  def scf_YieldOp(self, operands):
-    return ir.Operation.create("scf.yield",
-                               operands=operands,
-                               loc=self.loc,
-                               ip=self.ip)
-
 
 class ImportStage:
   """Base class for activities representing isolated import activity.
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/booleans.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/booleans.mlir
new file mode 100644
index 0000000..921d167
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/booleans.mlir
@@ -0,0 +1,64 @@
+// RUN: iree-dialects-opt -split-input-file --allow-unregistered-dialect -canonicalize %s | FileCheck --enable-var-scope --dump-input-filter=all %s
+
+// CHECK-LABEL: @fold_none
+iree_pydm.func @fold_none(%arg0 : !iree_pydm.none) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[F:.*]] = constant false -> !iree_pydm.bool
+  // CHECK: return %[[F]]
+  %0 = as_bool %arg0 : !iree_pydm.none -> !iree_pydm.bool
+  return %0 : !iree_pydm.bool
+}
+
+// -----
+// CHECK-LABEL: @elide_as_bool_from_bool
+iree_pydm.func @elide_as_bool_from_bool(%arg0 : !iree_pydm.bool) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: return %arg0
+  %0 = as_bool %arg0 : !iree_pydm.bool -> !iree_pydm.bool
+  return %0 : !iree_pydm.bool
+}
+
+// -----
+// CHECK-LABEL: @as_bool_from_integer_to_compare
+iree_pydm.func @as_bool_from_integer_to_compare(%arg0 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[Z:.*]] = constant 0 : i64 -> !iree_pydm.integer
+  // CHECK: %[[T:.*]] = constant true -> !iree_pydm.bool
+  // CHECK: %[[F:.*]] = constant false -> !iree_pydm.bool
+  // CHECK: %[[CMP:.*]] = apply_compare "eq", %arg0, %[[Z]]
+  // CHECK: %[[SEL:.*]] = select %[[CMP]], %[[F]], %[[T]] : !iree_pydm.bool
+  // CHECK: return %[[SEL]]
+  %0 = as_bool %arg0 : !iree_pydm.integer -> !iree_pydm.bool
+  return %0 : !iree_pydm.bool
+}
+
+// -----
+// CHECK-LABEL: @as_bool_from_real_to_compare
+iree_pydm.func @as_bool_from_real_to_compare(%arg0 : !iree_pydm.real) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  // CHECK: %[[Z:.*]] = constant 0.000000e+00 : f64 -> !iree_pydm.real
+  // CHECK: %[[T:.*]] = constant true -> !iree_pydm.bool
+  // CHECK: %[[F:.*]] = constant false -> !iree_pydm.bool
+  // CHECK: %[[CMP:.*]] = apply_compare "eq", %arg0, %[[Z]]
+  // CHECK: %[[SEL:.*]] = select %[[CMP]], %[[F]], %[[T]] : !iree_pydm.bool
+  // CHECK: return %[[SEL]]
+  %0 = as_bool %arg0 : !iree_pydm.real -> !iree_pydm.bool
+  return %0 : !iree_pydm.bool
+}
+
+// -----
+// CHECK-LABEL: @fold_bool_to_pred_from_constant
+iree_pydm.func @fold_bool_to_pred_from_constant() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  %0 = iree_pydm.constant true -> !iree_pydm.bool
+  // CHECK: %[[P:.*]] = constant true -> i1
+  // CHECK: "custom.donotoptimize"(%[[P]])
+  %1 = bool_to_pred %0
+  "custom.donotoptimize"(%1) : (i1) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @as_bool_from_real_to_compare
+iree_pydm.func @as_bool_from_real_to_compare(%arg0: !iree_pydm.integer, %arg1: !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.integer) {
+  // CHECK: return %arg0
+  %0 = constant true -> !iree_pydm.bool
+  %1 = select %0, %arg0, %arg1 : !iree_pydm.integer
+  return %1 : !iree_pydm.integer
+}
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/boxing.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/boxing.mlir
new file mode 100644
index 0000000..7df0433
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/boxing.mlir
@@ -0,0 +1,39 @@
+// RUN: iree-dialects-opt -split-input-file --allow-unregistered-dialect -canonicalize %s | FileCheck --enable-var-scope --dump-input-filter=all %s
+
+// CHECK-LABEL: @elide_boxing_noop
+iree_pydm.func @elide_boxing_noop(%arg0 : !iree_pydm.object) -> (!iree_pydm.exception_result, !iree_pydm.object) {
+  %0 = box %arg0 : !iree_pydm.object -> !iree_pydm.object
+  // CHECK: return %arg0
+  return %0 : !iree_pydm.object
+}
+
+// -----
+// CHECK-LABEL: @preserves_boxing
+iree_pydm.func @preserves_boxing(%arg0 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.object) {
+  // CHECK: %[[BOXED:.*]] = box %arg0
+  %0 = box %arg0 : !iree_pydm.integer -> !iree_pydm.object
+  // CHECK: return %[[BOXED]]
+  return %0 : !iree_pydm.object
+}
+
+// -----
+// CHECK-LABEL: @elide_unboxing_from_boxed_noop
+iree_pydm.func @elide_unboxing_from_boxed_noop(%arg0 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.integer) {
+  // CHECK-NOT: raise_on_failure
+  %0 = box %arg0 : !iree_pydm.integer -> !iree_pydm.object
+  %exc_result, %1 = unbox %0 : !iree_pydm.object -> !iree_pydm.integer
+  raise_on_failure %exc_result : !iree_pydm.exception_result
+  // CHECK: return %arg0
+  return %1 : !iree_pydm.integer
+}
+
+// -----
+// CHECK-LABEL: @preserve_unboxing
+iree_pydm.func @preserve_unboxing(%arg0 : !iree_pydm.object) -> (!iree_pydm.exception_result, !iree_pydm.integer) {
+  %0 = box %arg0 : !iree_pydm.object -> !iree_pydm.object
+  // CHECK: %[[STATUS:.*]], %[[UNBOXED:.*]] = unbox
+  %exc_result, %1 = unbox %0 : !iree_pydm.object -> !iree_pydm.integer
+  raise_on_failure %exc_result : !iree_pydm.exception_result
+  // CHECK: return %[[UNBOXED]]
+  return %1 : !iree_pydm.integer
+}
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/exceptions.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/exceptions.mlir
new file mode 100644
index 0000000..363af77
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/exceptions.mlir
@@ -0,0 +1,29 @@
+// RUN: iree-dialects-opt -split-input-file --allow-unregistered-dialect -canonicalize %s | FileCheck --enable-var-scope --dump-input-filter=all %s
+
+// CHECK-LABEL: @elide_raise_on_failure_success
+iree_pydm.func @elide_raise_on_failure_success() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK-NOT: raise_on_failure
+  %0 = success -> !iree_pydm.exception_result
+  raise_on_failure %0 : !iree_pydm.exception_result
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @preserve_raise_on_failure_failure
+iree_pydm.func @preserve_raise_on_failure_failure() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: raise_on_failure
+  %0 = failure -> !iree_pydm.exception_result
+  raise_on_failure %0 : !iree_pydm.exception_result
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @preserve_raise_on_failure_unknown
+iree_pydm.func @preserve_raise_on_failure_unknown(%arg0 : !iree_pydm.exception_result) -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: raise_on_failure
+  raise_on_failure %arg0 : !iree_pydm.exception_result
+  %none = none
+  return %none : !iree_pydm.none
+}
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/numerics.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/numerics.mlir
new file mode 100644
index 0000000..e302baa
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/canonicalize/numerics.mlir
@@ -0,0 +1,111 @@
+// RUN: iree-dialects-opt -split-input-file --allow-unregistered-dialect -canonicalize %s | FileCheck --enable-var-scope --dump-input-filter=all %s
+
+// CHECK-LABEL: @dynamic_binary_promote_same_type
+iree_pydm.func @dynamic_binary_promote_same_type(%arg0 : !iree_pydm.bool, %arg1 : !iree_pydm.bool) -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // Note: it is important that types are not modified as part of canonicalization,
+  // since the legality of that requires more analysis. Therefore, this must
+  // produce unrefined objects, like the original.
+  // CHECK: %[[LEFT:.*]] = box %arg0 : !iree_pydm.bool -> !iree_pydm.object
+  // CHECK: %[[RIGHT:.*]] = box %arg1 : !iree_pydm.bool -> !iree_pydm.object
+  // CHECK: "custom.donotoptimize"(%[[LEFT]], %[[RIGHT]])
+  %0, %1 = dynamic_binary_promote %arg0, %arg1 : !iree_pydm.bool, !iree_pydm.bool
+  "custom.donotoptimize"(%0, %1) : (!iree_pydm.object, !iree_pydm.object) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @dynamic_binary_promote_promote_left
+iree_pydm.func @dynamic_binary_promote_promote_left(%arg0 : !iree_pydm.bool, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: %[[LEFT:.*]] = promote_numeric %arg0 : !iree_pydm.bool -> !iree_pydm.integer
+  // CHECK: %[[LEFT_BOXED:.*]] = box %[[LEFT]] : !iree_pydm.integer -> !iree_pydm.object
+  // CHECK: %[[RIGHT_BOXED:.*]] = box %arg1 : !iree_pydm.integer -> !iree_pydm.object
+  // CHECK: "custom.donotoptimize"(%[[LEFT_BOXED]], %[[RIGHT_BOXED]])
+  %0, %1 = dynamic_binary_promote %arg0, %arg1 : !iree_pydm.bool, !iree_pydm.integer
+  "custom.donotoptimize"(%0, %1) : (!iree_pydm.object, !iree_pydm.object) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @dynamic_binary_promote_promote_right
+iree_pydm.func @dynamic_binary_promote_promote_right(%arg0 : !iree_pydm.real, %arg1 : !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: %[[RIGHT:.*]] = promote_numeric %arg1 : !iree_pydm.integer -> !iree_pydm.real
+  // CHECK: %[[LEFT_BOXED:.*]] = box %arg0 : !iree_pydm.real -> !iree_pydm.object
+  // CHECK: %[[RIGHT_BOXED:.*]] = box %[[RIGHT]] : !iree_pydm.real -> !iree_pydm.object
+  // CHECK: "custom.donotoptimize"(%[[LEFT_BOXED]], %[[RIGHT_BOXED]])
+  %0, %1 = dynamic_binary_promote %arg0, %arg1 : !iree_pydm.real, !iree_pydm.integer
+  "custom.donotoptimize"(%0, %1) : (!iree_pydm.object, !iree_pydm.object) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @elide_promote_numeric_identity
+iree_pydm.func @elide_promote_numeric_identity(%arg0: !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: "custom.donotoptimize"(%arg0)
+  %0 = promote_numeric %arg0 : !iree_pydm.integer -> !iree_pydm.integer
+  "custom.donotoptimize"(%0) : (!iree_pydm.integer) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @fold_promote_numeric_true_to_integer
+iree_pydm.func @fold_promote_numeric_true_to_integer() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: %[[CST:.*]] = constant 1 : i64 -> !iree_pydm.integer
+  // CHECK: "custom.donotoptimize"(%[[CST]])
+  %0 = constant true -> !iree_pydm.bool
+  %1 = promote_numeric %0 : !iree_pydm.bool -> !iree_pydm.integer
+  "custom.donotoptimize"(%1) : (!iree_pydm.integer) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @fold_promote_numeric_false_to_integer
+iree_pydm.func @fold_promote_numeric_false_to_integer() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: %[[CST:.*]] = constant 0 : i64 -> !iree_pydm.integer
+  // CHECK: "custom.donotoptimize"(%[[CST]])
+  %0 = constant false -> !iree_pydm.bool
+  %1 = promote_numeric %0 : !iree_pydm.bool -> !iree_pydm.integer
+  "custom.donotoptimize"(%1) : (!iree_pydm.integer) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @fold_promote_numeric_true_to_real
+iree_pydm.func @fold_promote_numeric_true_to_real() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: %[[CST:.*]] = constant 1.000000e+00 : f64 -> !iree_pydm.real
+  // CHECK: "custom.donotoptimize"(%[[CST]])
+  %0 = constant true -> !iree_pydm.bool
+  %1 = promote_numeric %0 : !iree_pydm.bool -> !iree_pydm.real
+  "custom.donotoptimize"(%1) : (!iree_pydm.real) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @fold_promote_numeric_false_to_real
+iree_pydm.func @fold_promote_numeric_false_to_real() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: %[[CST:.*]] = constant 0.000000e+00 : f64 -> !iree_pydm.real
+  // CHECK: "custom.donotoptimize"(%[[CST]])
+  %0 = constant false -> !iree_pydm.bool
+  %1 = promote_numeric %0 : !iree_pydm.bool -> !iree_pydm.real
+  "custom.donotoptimize"(%1) : (!iree_pydm.real) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
+
+// -----
+// CHECK-LABEL: @fold_promote_numeric_integet_to_real
+iree_pydm.func @fold_promote_numeric_integet_to_real() -> (!iree_pydm.exception_result, !iree_pydm.none) {
+  // CHECK: %[[CST:.*]] = constant 2.000000e+00 : f64 -> !iree_pydm.real
+  // CHECK: "custom.donotoptimize"(%[[CST]])
+  %0 = constant 2 : i64 -> !iree_pydm.integer
+  %1 = promote_numeric %0 : !iree_pydm.integer -> !iree_pydm.real
+  "custom.donotoptimize"(%1) : (!iree_pydm.real) -> ()
+  %none = none
+  return %none : !iree_pydm.none
+}
diff --git a/llvm-external-projects/iree-dialects/test/iree_pydm/ops_types_parse.mlir b/llvm-external-projects/iree-dialects/test/iree_pydm/ops_types_parse.mlir
new file mode 100644
index 0000000..5b908ce
--- /dev/null
+++ b/llvm-external-projects/iree-dialects/test/iree_pydm/ops_types_parse.mlir
@@ -0,0 +1,15 @@
+// RUN: iree-dialects-opt %s | iree-dialects-opt
+
+iree_pydm.func @free_var(%arg0 : !iree_pydm.bool) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  %var = alloc_free_var "foo" -> !iree_pydm.free_var_ref
+  store_var %var = %arg0 : !iree_pydm.free_var_ref, !iree_pydm.bool
+  %0 = load_var %var : !iree_pydm.free_var_ref -> !iree_pydm.bool
+  return %0 : !iree_pydm.bool
+}
+
+iree_pydm.func @free_var_index(%arg0 : !iree_pydm.bool) -> (!iree_pydm.exception_result, !iree_pydm.bool) {
+  %var = alloc_free_var "foo"[1] -> !iree_pydm.free_var_ref
+  store_var %var = %arg0 : !iree_pydm.free_var_ref, !iree_pydm.bool
+  %0 = load_var %var : !iree_pydm.free_var_ref -> !iree_pydm.bool
+  return %0 : !iree_pydm.bool
+}
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/assignment.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/assignment.py
index 8b32f08..a8d116f 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/assignment.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/assignment.py
@@ -5,9 +5,9 @@
 
 
 # CHECK-LABEL: @assign_free_var_not_arg
-# CHECK: %[[CST:.*]] = iree_pydm.constant 1
-# CHECK: %[[BOXED:.*]] = iree_pydm.box %[[CST]] : !iree_pydm.integer -> !iree_pydm.object<!iree_pydm.integer>
-# CHECK: iree_pydm.store_free_var "x", %[[BOXED]]
+# CHECK: %[[CST:.*]] = constant 1
+# CHECK: %[[BOXED:.*]] = box %[[CST]] : !iree_pydm.integer -> !iree_pydm.object<!iree_pydm.integer>
+# CHECK: store_var %x = %[[BOXED]]
 @test_import_global
 def assign_free_var_not_arg():
   x = 1
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/binary.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/binary.py
index 888536b..d9d3d29 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/binary.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/binary.py
@@ -5,94 +5,94 @@
 
 
 # CHECK-LABEL: @binary_add
-# CHECK: %[[L:.*]] = iree_pydm.load_free_var "a"
-# CHECK: %[[R:.*]] = iree_pydm.load_free_var "b"
-# CHECK: %[[LP:.*]], %[[RP:.*]] = iree_pydm.dynamic_binary_promote %[[L]], %[[R]]
-# CHECK: iree_pydm.apply_binary "add", %[[LP]], %[[RP]]
+# CHECK: %[[L:.*]] = load_var %a
+# CHECK: %[[R:.*]] = load_var %b
+# CHECK: %[[LP:.*]], %[[RP:.*]] = dynamic_binary_promote %[[L]], %[[R]]
+# CHECK: apply_binary "add", %[[LP]], %[[RP]]
 @test_import_global
 def binary_add(a, b):
   return a + b
 
 
 # CHECK-LABEL: @binary_sub
-# CHECK: iree_pydm.apply_binary "sub"
+# CHECK: apply_binary "sub"
 @test_import_global
 def binary_sub(a, b):
   return a - b
 
 
 # CHECK-LABEL: @binary_mul
-# CHECK: iree_pydm.apply_binary "mul"
+# CHECK: apply_binary "mul"
 @test_import_global
 def binary_mul(a, b):
   return a * b
 
 
 # CHECK-LABEL: @binary_matmul
-# CHECK: iree_pydm.apply_binary "matmul"
+# CHECK: apply_binary "matmul"
 @test_import_global
 def binary_matmul(a, b):
   return a @ b
 
 
 # CHECK-LABEL: @binary_truediv
-# CHECK: iree_pydm.apply_binary "truediv"
+# CHECK: apply_binary "truediv"
 @test_import_global
 def binary_truediv(a, b):
   return a / b
 
 
 # CHECK-LABEL: @binary_floordiv
-# CHECK: iree_pydm.apply_binary "floordiv"
+# CHECK: apply_binary "floordiv"
 @test_import_global
 def binary_floordiv(a, b):
   return a // b
 
 
 # CHECK-LABEL: @binary_mod
-# CHECK: iree_pydm.apply_binary "mod"
+# CHECK: apply_binary "mod"
 @test_import_global
 def binary_mod(a, b):
   return a % b
 
 
 # CHECK-LABEL: @binary_pow
-# CHECK: iree_pydm.apply_binary "pow"
+# CHECK: apply_binary "pow"
 @test_import_global
 def binary_pow(a, b):
   return a**b
 
 
 # CHECK-LABEL: @binary_lshift
-# CHECK: iree_pydm.apply_binary "lshift"
+# CHECK: apply_binary "lshift"
 @test_import_global
 def binary_lshift(a, b):
   return a << b
 
 
 # CHECK-LABEL: @binary_rshift
-# CHECK: iree_pydm.apply_binary "rshift"
+# CHECK: apply_binary "rshift"
 @test_import_global
 def binary_rshift(a, b):
   return a >> b
 
 
 # CHECK-LABEL: @binary_and
-# CHECK: iree_pydm.apply_binary "and"
+# CHECK: apply_binary "and"
 @test_import_global
 def binary_and(a, b):
   return a & b
 
 
 # CHECK-LABEL: @binary_or
-# CHECK: iree_pydm.apply_binary "or"
+# CHECK: apply_binary "or"
 @test_import_global
 def binary_or(a, b):
   return a | b
 
 
 # CHECK-LABEL: @binary_xor
-# CHECK: iree_pydm.apply_binary "xor"
+# CHECK: apply_binary "xor"
 @test_import_global
 def binary_xor(a, b):
   return a ^ b
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/booleans.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/booleans.py
index 63461b4..93f547a 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/booleans.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/booleans.py
@@ -5,22 +5,20 @@
 
 
 # CHECK-LABEL: @logical_and
-# CHECK: %[[XVAL:.*]] = iree_pydm.load_free_var "x"
-# CHECK: %[[XBOOL:.*]] = iree_pydm.as_bool %[[XVAL]]
-# CHECK: %[[XPRED:.*]] = iree_pydm.bool_to_pred %[[XBOOL]]
-# CHECK: %[[R1:.*]] = scf.if %[[XPRED]] {{.*}} {
-# CHECK:   %[[YVAL:.*]] = iree_pydm.load_free_var "y"
-# CHECK:   %[[YBOOL:.*]] = iree_pydm.as_bool %[[YVAL]]
-# CHECK:   %[[YPRED:.*]] = iree_pydm.bool_to_pred %[[YBOOL]]
-# CHECK:   %[[R2:.*]] = scf.if %[[YPRED]] {{.*}} {
-# CHECK:     %[[ZVAL:.*]] = iree_pydm.load_free_var "z"
-# CHECK:     scf.yield %[[ZVAL]]
+# CHECK: %[[XVAL:.*]] = load_var %x
+# CHECK: %[[XBOOL:.*]] = as_bool %[[XVAL]]
+# CHECK: %[[R1:.*]] = functional_if %[[XBOOL]] {{.*}} {
+# CHECK:   %[[YVAL:.*]] = load_var %y
+# CHECK:   %[[YBOOL:.*]] = as_bool %[[YVAL]]
+# CHECK:   %[[R2:.*]] = functional_if %[[YBOOL]] {{.*}} {
+# CHECK:     %[[ZVAL:.*]] = load_var %z
+# CHECK:     yield %[[ZVAL]]
 # CHECK:   } else {
-# CHECK:     scf.yield %[[YVAL]]
+# CHECK:     yield %[[YVAL]]
 # CHECK:   }
-# CHECK:   scf.yield %[[R2]]
+# CHECK:   yield %[[R2]]
 # CHECK: } else {
-# CHECK:   scf.yield %[[XVAL]]
+# CHECK:   yield %[[XVAL]]
 # CHECK: }
 @test_import_global
 def logical_and():
@@ -31,22 +29,20 @@
 
 
 # # CHECK-LABEL: @logical_or
-# CHECK: %[[XVAL:.*]] = iree_pydm.load_free_var "x"
-# CHECK: %[[XBOOL:.*]] = iree_pydm.as_bool %[[XVAL]]
-# CHECK: %[[XPRED:.*]] = iree_pydm.bool_to_pred %[[XBOOL]]
-# CHECK: %[[R1:.*]] = scf.if %[[XPRED]] {{.*}} {
-# CHECK:   scf.yield %[[XVAL]]
+# CHECK: %[[XVAL:.*]] = load_var %x
+# CHECK: %[[XBOOL:.*]] = as_bool %[[XVAL]]
+# CHECK: %[[R1:.*]] = functional_if %[[XBOOL]] {{.*}} {
+# CHECK:   yield %[[XVAL]]
 # CHECK: } else {
-# CHECK:   %[[YVAL:.*]] = iree_pydm.load_free_var "y"
-# CHECK:   %[[YBOOL:.*]] = iree_pydm.as_bool %[[YVAL]]
-# CHECK:   %[[YPRED:.*]] = iree_pydm.bool_to_pred %[[YBOOL]]
-# CHECK:   %[[R2:.*]] = scf.if %[[YPRED]] {{.*}} {
-# CHECK:     scf.yield %[[YVAL]]
+# CHECK:   %[[YVAL:.*]] = load_var %y
+# CHECK:   %[[YBOOL:.*]] = as_bool %[[YVAL]]
+# CHECK:   %[[R2:.*]] = functional_if %[[YBOOL]] {{.*}} {
+# CHECK:     yield %[[YVAL]]
 # CHECK:   } else {
-# CHECK:     %[[ZVAL:.*]] = iree_pydm.load_free_var "z"
-# CHECK:     scf.yield %[[ZVAL]]
+# CHECK:     %[[ZVAL:.*]] = load_var %z
+# CHECK:     yield %[[ZVAL]]
 # CHECK:   }
-# CHECK:   scf.yield %[[R2]]
+# CHECK:   yield %[[R2]]
 # CHECK: }
 @test_import_global
 def logical_or():
@@ -57,11 +53,11 @@
 
 
 # CHECK-LABEL: func @logical_not
-# CHECK: %[[XVAL:.*]] = iree_pydm.load_free_var "x"
-# CHECK: %[[XBOOL:.*]] = iree_pydm.as_bool %[[XVAL]]
-# CHECK: %[[T:.*]] = iree_pydm.constant true
-# CHECK: %[[F:.*]] = iree_pydm.constant false
-# CHECK: %[[R:.*]] = iree_pydm.select %[[XBOOL]], %[[F]], %[[T]]
+# CHECK: %[[XVAL:.*]] = load_var %x
+# CHECK: %[[XBOOL:.*]] = as_bool %[[XVAL]]
+# CHECK: %[[T:.*]] = constant true
+# CHECK: %[[F:.*]] = constant false
+# CHECK: %[[R:.*]] = select %[[XBOOL]], %[[F]], %[[T]]
 @test_import_global
 def logical_not():
   x = 1
@@ -69,17 +65,16 @@
 
 
 # CHECK-LABEL: func @conditional
-# CHECK: %[[XVAL:.*]] = iree_pydm.load_free_var "x"
-# CHECK: %[[XBOOL:.*]] = iree_pydm.as_bool %[[XVAL]]
-# CHECK: %[[XPRED:.*]] = iree_pydm.bool_to_pred %[[XBOOL]]
-# CHECK: %[[R1:.*]] = scf.if %[[XPRED]] {{.*}} {
-# CHECK:   %[[TWOVAL:.*]] = iree_pydm.constant 2
-# CHECK:   %[[TWOBOXED:.*]] = iree_pydm.box %[[TWOVAL]] : !iree_pydm.integer -> !iree_pydm.object
-# CHECK:   scf.yield %[[TWOBOXED]]
+# CHECK: %[[XVAL:.*]] = load_var %x
+# CHECK: %[[XBOOL:.*]] = as_bool %[[XVAL]]
+# CHECK: %[[R1:.*]] = functional_if %[[XBOOL]] {{.*}} {
+# CHECK:   %[[TWOVAL:.*]] = constant 2
+# CHECK:   %[[TWOBOXED:.*]] = box %[[TWOVAL]] : !iree_pydm.integer -> !iree_pydm.object
+# CHECK:   yield %[[TWOBOXED]]
 # CHECK: } else {
-# CHECK:   %[[THREEVAL:.*]] = iree_pydm.constant 3
-# CHECK:   %[[THREEBOXED:.*]] = iree_pydm.box %[[THREEVAL]] : !iree_pydm.integer -> !iree_pydm.object
-# CHECK:   scf.yield %[[THREEBOXED]]
+# CHECK:   %[[THREEVAL:.*]] = constant 3
+# CHECK:   %[[THREEBOXED:.*]] = box %[[THREEVAL]] : !iree_pydm.integer -> !iree_pydm.object
+# CHECK:   yield %[[THREEBOXED]]
 # CHECK: }
 @test_import_global
 def conditional():
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/comparison.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/comparison.py
index 213c9f5..4c85035 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/comparison.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/comparison.py
@@ -8,10 +8,10 @@
 
 
 # CHECK-LABEL: func @binary_lt_
-# CHECK-DAG: %[[L:.*]] = iree_pydm.load_free_var "x"
-# CHECK-DAG: %[[R:.*]] = iree_pydm.load_free_var "y"
-# CHECK: %[[LP:.*]], %[[RP:.*]] = iree_pydm.dynamic_binary_promote %[[L]], %[[R]]
-# CHECK: iree_pydm.apply_compare "lt", %[[LP]], %[[RP]]
+# CHECK-DAG: %[[L:.*]] = load_var %x
+# CHECK-DAG: %[[R:.*]] = load_var %y
+# CHECK: %[[LP:.*]], %[[RP:.*]] = dynamic_binary_promote %[[L]], %[[R]]
+# CHECK: apply_compare "lt", %[[LP]], %[[RP]]
 @test_import_global
 def binary_lt_():
   x = 1
@@ -20,8 +20,8 @@
 
 
 # CHECK-LABEL: func @binary_gt_
-# CHECK: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "gt"
+# CHECK: dynamic_binary_promote
+# CHECK: apply_compare "gt"
 @test_import_global
 def binary_gt_():
   x = 1
@@ -30,8 +30,8 @@
 
 
 # CHECK-LABEL: func @binary_lte_
-# CHECK: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "le"
+# CHECK: dynamic_binary_promote
+# CHECK: apply_compare "le"
 @test_import_global
 def binary_lte_():
   x = 1
@@ -40,8 +40,8 @@
 
 
 # CHECK-LABEL: func @binary_gte_
-# CHECK: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "ge"
+# CHECK: dynamic_binary_promote
+# CHECK: apply_compare "ge"
 @test_import_global
 def binary_gte_():
   x = 1
@@ -50,8 +50,8 @@
 
 
 # CHECK-LABEL: func @binary_eq_
-# CHECK: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "eq"
+# CHECK: dynamic_binary_promote
+# CHECK: apply_compare "eq"
 @test_import_global
 def binary_eq_():
   x = 1
@@ -60,8 +60,8 @@
 
 
 # CHECK-LABEL: func @binary_neq_
-# CHECK: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "ne"
+# CHECK: dynamic_binary_promote
+# CHECK: apply_compare "ne"
 @test_import_global
 def binary_neq_():
   x = 1
@@ -70,8 +70,8 @@
 
 
 # CHECK-LABEL: func @binary_is_
-# CHECK-NOT: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "is"
+# CHECK-NOT: dynamic_binary_promote
+# CHECK: apply_compare "is"
 @test_import_global
 def binary_is_():
   x = 1
@@ -80,8 +80,8 @@
 
 
 # CHECK-LABEL: func @binary_is_not_
-# CHECK-NOT: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "isnot"
+# CHECK-NOT: dynamic_binary_promote
+# CHECK: apply_compare "isnot"
 @test_import_global
 def binary_is_not_():
   x = 1
@@ -90,8 +90,8 @@
 
 
 # CHECK-LABEL: func @binary_in_
-# CHECK-NOT: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "in"
+# CHECK-NOT: dynamic_binary_promote
+# CHECK: apply_compare "in"
 @test_import_global
 def binary_in_():
   x = 1
@@ -100,8 +100,8 @@
 
 
 # CHECK-LABEL: func @binary_not_in_
-# CHECK-NOT: iree_pydm.dynamic_binary_promote
-# CHECK: iree_pydm.apply_compare "notin"
+# CHECK-NOT: dynamic_binary_promote
+# CHECK: apply_compare "notin"
 @test_import_global
 def binary_not_in_():
   x = 1
@@ -110,30 +110,28 @@
 
 
 # CHECK-LABEL: @short_circuit
-# CHECK-DAG: %[[FALSE:.*]] = iree_pydm.constant false
-# CHECK-DAG: %[[X:.*]] = iree_pydm.load_free_var "x"
-# CHECK-DAG: %[[Y:.*]] = iree_pydm.load_free_var "y"
-# CHECK: %[[XP:.*]], %[[YP:.*]] = iree_pydm.dynamic_binary_promote %[[X]], %[[Y]]
-# CHECK: %[[R1:.*]] = iree_pydm.apply_compare "lt", %[[XP]], %[[YP]]
-# CHECK: %[[RP1:.*]] = iree_pydm.bool_to_pred %[[R1]]
-# CHECK: %[[RESULT:.*]] = scf.if %[[RP1]] {{.*}} {
-# CHECK:   %[[Z:.*]] = iree_pydm.load_free_var "z"
+# CHECK-DAG: %[[FALSE:.*]] = constant false
+# CHECK-DAG: %[[X:.*]] = load_var %x
+# CHECK-DAG: %[[Y:.*]] = load_var %y
+# CHECK: %[[XP:.*]], %[[YP:.*]] = dynamic_binary_promote %[[X]], %[[Y]]
+# CHECK: %[[R1:.*]] = apply_compare "lt", %[[XP]], %[[YP]]
+# CHECK: %[[RESULT:.*]] = functional_if %[[R1]] {{.*}} {
+# CHECK:   %[[Z:.*]] = load_var %z
 # NOTE: Promotion happens on original loaded values, not already promoted
 # values.
-# CHECK:   %[[YP1:.*]], %[[ZP1:.*]] = iree_pydm.dynamic_binary_promote %[[Y]], %[[Z]]
-# CHECK:   %[[R2:.*]] = iree_pydm.apply_compare "eq", %[[YP1]], %[[ZP1]]
-# CHECK:   %[[RP2:.*]] = iree_pydm.bool_to_pred %[[R2]]
-# CHECK:   %[[RESULT1:.*]] = scf.if %[[RP2]] {{.*}} {
-# CHECK:     %[[OMEGA:.*]] = iree_pydm.load_free_var "omega"
-# CHECK:     %[[ZP2:.*]], %[[OMEGAP2:.*]] = iree_pydm.dynamic_binary_promote %[[Z]], %[[OMEGA]]
-# CHECK:     %[[R3:.*]] = iree_pydm.apply_compare "ge", %[[ZP2]], %[[OMEGAP2]]
-# CHECK:     scf.yield %[[R3]]
+# CHECK:   %[[YP1:.*]], %[[ZP1:.*]] = dynamic_binary_promote %[[Y]], %[[Z]]
+# CHECK:   %[[R2:.*]] = apply_compare "eq", %[[YP1]], %[[ZP1]]
+# CHECK:   %[[RESULT1:.*]] = functional_if %[[R2]] {{.*}} {
+# CHECK:     %[[OMEGA:.*]] = load_var %omega
+# CHECK:     %[[ZP2:.*]], %[[OMEGAP2:.*]] = dynamic_binary_promote %[[Z]], %[[OMEGA]]
+# CHECK:     %[[R3:.*]] = apply_compare "ge", %[[ZP2]], %[[OMEGAP2]]
+# CHECK:     yield %[[R3]]
 # CHECK:   } else {
-# CHECK:     scf.yield %[[FALSE]]
+# CHECK:     yield %[[FALSE]]
 # CHECK:   }
-# CHECK:   scf.yield %[[RESULT1]]
+# CHECK:   yield %[[RESULT1]]
 # CHECK: } else {
-# CHECK:   scf.yield %[[FALSE]]
+# CHECK:   yield %[[FALSE]]
 # CHECK: }
 @test_import_global
 def short_circuit():
@@ -146,8 +144,8 @@
 
 # CHECK-LABEL: nested_short_circuit_expression
 # Verify that the nested expression is evaluated in the context of the if.
-# CHECK: scf.if {{.*}} {
-# CHECK:   iree_pydm.apply_binary "add"
+# CHECK: functional_if {{.*}} {
+# CHECK:   apply_binary "add"
 # CHECK: } else {
 @test_import_global
 def nested_short_circuit_expression():
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/constants.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/constants.py
index 1a2c268..05218d2 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/constants.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/constants.py
@@ -5,49 +5,49 @@
 
 
 # CHECK-LABEL: @const_integer
-# CHECK: iree_pydm.constant 1 : si64 -> !iree_pydm.integer
+# CHECK: = constant 1 : i64 -> !iree_pydm.integer
 @test_import_global
 def const_integer():
   return 1
 
 
 # CHECK-LABEL: @const_float
-# CHECK: iree_pydm.constant 2.200000e+00 : f64 -> !iree_pydm.real
+# CHECK: = constant 2.200000e+00 : f64 -> !iree_pydm.real
 @test_import_global
 def const_float():
   return 2.2
 
 
 # CHECK-LABEL: @const_str
-# CHECK: iree_pydm.constant "Hello" -> !iree_pydm.str
+# CHECK: = constant "Hello" -> !iree_pydm.str
 @test_import_global
 def const_str():
   return "Hello"
 
 
 # CHECK-LABEL: @const_bytes
-# CHECK: iree_pydm.constant "Bonjour" -> !iree_pydm.bytes
+# CHECK: = constant "Bonjour" -> !iree_pydm.bytes
 @test_import_global
 def const_bytes():
   return b"Bonjour"
 
 
 # CHECK-LABEL: @const_none
-# CHECK: iree_pydm.none -> !iree_pydm.none
+# CHECK: = none
 @test_import_global
 def const_none():
   return None
 
 
 # CHECK-LABEL: @const_true
-# CHECK: iree_pydm.constant true -> !iree_pydm.bool
+# CHECK: = constant true -> !iree_pydm.bool
 @test_import_global
 def const_true():
   return True
 
 
 # CHECK-LABEL: @const_false
-# CHECK: iree_pydm.constant false -> !iree_pydm.bool
+# CHECK: = constant false -> !iree_pydm.bool
 @test_import_global
 def const_false():
   return False
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/flow_control.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/flow_control.py
index 7101dde..fa568d1 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/flow_control.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/flow_control.py
@@ -5,15 +5,15 @@
 
 
 # CHECK-LABEL: @simple_if
-# CHECK: %[[COND:.*]] = iree_pydm.load_free_var "cond"
-# CHECK: %[[COND_BOOL:.*]] = iree_pydm.as_bool %[[COND]]
-# CHECK: %[[COND_PRED:.*]] = iree_pydm.bool_to_pred %[[COND_BOOL]]
+# CHECK: %[[COND:.*]] = load_var %cond
+# CHECK: %[[COND_BOOL:.*]] = as_bool %[[COND]]
+# CHECK: %[[COND_PRED:.*]] = bool_to_pred %[[COND_BOOL]]
 # CHECK: cond_br %2, ^bb1, ^bb2
 # CHECK: ^bb1:
-# CHECK: %[[A:.*]] = iree_pydm.load_free_var "a"
+# CHECK: %[[A:.*]] = load_var %a
 # CHECK: return %[[A]]
 # CHECK: ^bb2:
-# CHECK: %[[B:.*]] = iree_pydm.load_free_var "b"
+# CHECK: %[[B:.*]] = load_var %b
 # CHECK: return %[[B]]
 @test_import_global
 def simple_if(cond, a, b):
@@ -30,7 +30,7 @@
 # CHECK: ^bb2:
 # CHECK: br ^bb3
 # CHECK: ^bb3:
-# CHECK: iree_pydm.return
+# CHECK: return
 @test_import_global
 def if_fallthrough(cond, a, b):
   if cond:
@@ -45,7 +45,7 @@
 # CHECK: ^bb1:
 # CHECK: br ^bb2
 # CHECK: ^bb2:
-# CHECK: iree_pydm.return
+# CHECK: return
 @test_import_global
 def if_noelse(cond, a, b):
   c = 1
@@ -67,7 +67,7 @@
 # CHECK: ^bb5:
 # CHECK: br ^bb6
 # CHECK: ^bb6:
-# CHECK: iree_pydm.return
+# CHECK: return
 @test_import_global
 def if_elif(cond, a, b):
   if cond:
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/function_def.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/function_def.py
index fc251347..5bb592a 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/function_def.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/function_def.py
@@ -4,18 +4,18 @@
 from mlir.dialects.iree_pydm.importer.test_util import *
 
 
-# CHECK-LABEL: iree_pydm.func @fully_typed_with_return
+# CHECK-LABEL: func @fully_typed_with_return
 # CHECK-SAME: (%arg0: !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.integer)
 # CHECK-SAME: attributes {arg_names = ["a"], cell_vars = [], free_vars = ["a"]}
-# CHECK: iree_pydm.return {{.*}} : !iree_pydm.integer
+# CHECK: return {{.*}} : !iree_pydm.integer
 @test_import_global
 def fully_typed_with_return(a: int) -> int:
   return a
 
 
-# CHECK-LABEL: iree_pydm.func @no_return
-# CHECK: %[[NONE:.*]] = iree_pydm.none
-# CHECK: iree_pydm.return %[[NONE]]
+# CHECK-LABEL: func @no_return
+# CHECK: %[[NONE:.*]] = none
+# CHECK: return %[[NONE]]
 @test_import_global
 def no_return():
   pass
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/intrinsics.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/intrinsics.py
index b561c45..ad7e2b0 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/intrinsics.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/intrinsics.py
@@ -23,8 +23,8 @@
 
 
 # CHECK-LABEL: @test_intrinsic_function_no_args
-# CHECK: iree_pydm.dynamic_call @__return_one() : () -> (!iree_pydm.exception_result, !iree_pydm.object)
-# CHECK: iree_pydm.func private @__return_one()
+# CHECK: dynamic_call @__return_one() : () -> (!iree_pydm.exception_result, !iree_pydm.object)
+# CHECK: func private @__return_one()
 @test_import_global
 def test_intrinsic_function_no_args():
   value = intrinsic_return_one()
@@ -41,10 +41,10 @@
 
 
 # CHECK-LABEL: @test_intrinsic_function_args
-# CHECK: %[[ZERO:.*]] = iree_pydm.constant 0 : si64 -> !iree_pydm.integer
-# CHECK: %[[ONE:.*]] = iree_pydm.constant 1 : si64 -> !iree_pydm.integer
-# CHECK: iree_pydm.dynamic_call @__return_first_true(%[[ZERO]], %[[ONE]]) : (!iree_pydm.integer, !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.object)
-# CHECK: iree_pydm.func private @__return_first_true
+# CHECK: %[[ZERO:.*]] = constant 0 : i64 -> !iree_pydm.integer
+# CHECK: %[[ONE:.*]] = constant 1 : i64 -> !iree_pydm.integer
+# CHECK: dynamic_call @__return_first_true(%[[ZERO]], %[[ONE]]) : (!iree_pydm.integer, !iree_pydm.integer) -> (!iree_pydm.exception_result, !iree_pydm.object)
+# CHECK: func private @__return_first_true
 @test_import_global
 def test_intrinsic_function_args():
   value = intrinsic_return_first_true(0, 1)
@@ -68,8 +68,8 @@
 
 
 # CHECK-LABEL: @test_intrinsic_macro_no_args
-# CHECK: %[[ONE:.*]] = iree_pydm.constant 1
-# CHECK: iree_pydm.box %[[ONE]] : !iree_pydm.integer -> !iree_pydm.object<!iree_pydm.integer>
+# CHECK: %[[ONE:.*]] = constant 1
+# CHECK: box %[[ONE]] : !iree_pydm.integer -> !iree_pydm.object<!iree_pydm.integer>
 @test_import_global
 def test_intrinsic_macro_no_args() -> int:
   return macro_box_arg(1)
@@ -95,11 +95,11 @@
 
 
 # CHECK-LABEL: @test_pattern_call
-# CHECK: %[[TRUE:.*]] = iree_pydm.constant true
-# CHECK: iree_pydm.pattern_match_call(%[[TRUE]]) : (!iree_pydm.bool) -> (!iree_pydm.exception_result, !iree_pydm.object)
+# CHECK: %[[TRUE:.*]] = constant true
+# CHECK: pattern_match_call(%[[TRUE]]) : (!iree_pydm.bool) -> (!iree_pydm.exception_result, !iree_pydm.object)
 # CHECK-SAME:   matching generic [@__logical_not_generic] specific [@__logical_not_bool]
-# CHECK-DAG: iree_pydm.func private @__logical_not_generic
-# CHECK-DAG: iree_pydm.func private @__logical_not_bool
+# CHECK-DAG: func private @__logical_not_generic
+# CHECK-DAG: func private @__logical_not_bool
 @test_import_global
 def test_pattern_call():
   return logical_not(True)
diff --git a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/structural.py b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/structural.py
index 35a6ac0..0dda414 100644
--- a/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/structural.py
+++ b/llvm-external-projects/iree-dialects/test/python/iree_pydm/importer/structural.py
@@ -5,8 +5,8 @@
 
 
 # CHECK-LABEL @expr_statement
-# CHECK: %[[XVAL:.*]] = iree_pydm.load_free_var "x"
-# CHECK: iree_pydm.expr_statement_discard %[[XVAL]]
+# CHECK: %[[XVAL:.*]] = load_var %x
+# CHECK: expr_statement_discard %[[XVAL]]
 @test_import_global
 def expr_statement(x: int):
   x
diff --git a/llvm-external-projects/iree-dialects/tools/iree-dialects-opt/iree-dialects-opt.cpp b/llvm-external-projects/iree-dialects/tools/iree-dialects-opt/iree-dialects-opt.cpp
index afc7e09..8f1dc39 100644
--- a/llvm-external-projects/iree-dialects/tools/iree-dialects-opt/iree-dialects-opt.cpp
+++ b/llvm-external-projects/iree-dialects/tools/iree-dialects-opt/iree-dialects-opt.cpp
@@ -5,7 +5,7 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 #include "iree-dialects/Dialect/IREE/IREEDialect.h"
-#include "iree-dialects/Dialect/IREEPyDM/IR/IREEPyDMDialect.h"
+#include "iree-dialects/Dialect/IREEPyDM/IR/Dialect.h"
 #include "mlir/Dialect/SCF/SCF.h"
 #include "mlir/Dialect/StandardOps/IR/Ops.h"
 #include "mlir/IR/AsmState.h"