[mpact][passes] Setup MPACT python pipeline interface. (#20)

* [mpact][passes] WIPs...

* [mpact][passes] use mpact pass manager

* update readme
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a9964e3..38d9f26 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,6 +57,18 @@
   endif()
 endfunction()
 
+list(APPEND CMAKE_MODULE_PATH ${MLIR_MAIN_SRC_DIR}/cmake/modules)
+list(APPEND CMAKE_MODULE_PATH ${LLVM_MAIN_SRC_DIR}/cmake)
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/cmake)
+
+include(TableGen)
+include(AddLLVM)
+include(AddMLIR)
+include(AddMLIRPython)
+
+include(MLIRDetectPythonEnv)
+mlir_configure_python_dev_packages()
+
 add_subdirectory(include)
 add_subdirectory(lib)
 add_subdirectory(tools)
diff --git a/README.md b/README.md
index b415e3f..8704d35 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@
 Also make sure to set the Python paths as follows.
 
 ```shell
-export PYTHONPATH=`pwd`/build/tools/torch-mlir/python_packages/torch_mlir:`pwd`/build/tools/mpact/python_packages/mpact
+export PYTHONPATH=`pwd`/build/tools/mpact/python_packages/mpact
 ```
 
 ### Install build requirements
diff --git a/include/mpact-c/Registration.h b/include/mpact-c/Registration.h
new file mode 100644
index 0000000..877b06c
--- /dev/null
+++ b/include/mpact-c/Registration.h
@@ -0,0 +1,27 @@
+/*===-- mpact-c/Registration.h - Registration functions  -----*- C -*-===*\
+|*                                                                            *|
+|* Part of the MPACT Project, 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 MPACT_C_REGISTRATION_H
+#define MPACT_C_REGISTRATION_H
+
+#include "mlir-c/IR.h"
+#include "mlir-c/Support.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Registers all passes for symbolic access with the global registry. */
+MLIR_CAPI_EXPORTED void mpactRegisterAllPasses(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MPACT_C_REGISTRATION_H
diff --git a/lib/CAPI/CMakeLists.txt b/lib/CAPI/CMakeLists.txt
new file mode 100644
index 0000000..f45c469
--- /dev/null
+++ b/lib/CAPI/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_mlir_public_c_api_library(MPACTCAPI
+  Registration.cpp
+
+  ENABLE_AGGREGATION
+
+  LINK_LIBS PUBLIC
+  MLIRIR
+  MLIRSupport
+  MPACTTransformPasses
+)
+
+mpact_target_includes(MPACTCAPI)
diff --git a/lib/CAPI/Registration.cpp b/lib/CAPI/Registration.cpp
new file mode 100644
index 0000000..51234fb
--- /dev/null
+++ b/lib/CAPI/Registration.cpp
@@ -0,0 +1,20 @@
+//===- Registration.cpp - C Interface for MLIR Registration ---------------===//
+//
+// Part of the LLVM Project, 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
+// Also available under a BSD-style license. See LICENSE.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mpact-c/Registration.h"
+
+#include "mlir/CAPI/IR.h"
+#include "mlir/Conversion/Passes.h"
+#include "mlir/Dialect/Linalg/Passes.h"
+#include "mlir/Transforms/Passes.h"
+#include "mpact/Transforms/Passes.h"
+
+MLIR_CAPI_EXPORTED void mpactRegisterAllPasses() {
+  mlir::mpact::registerTransformPasses();
+}
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index e31af32..9e7dcd1 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -1 +1,2 @@
+add_subdirectory(CAPI)
 add_subdirectory(Transforms)
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index 4b3953a..12c46bd 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -2,16 +2,40 @@
 # The MPACT Compiler Python Modules
 #-------------------------------------------------------------------------------
 
+# Disables generation of "version soname" (i.e. libFoo.so.<version>), which
+# causes pure duplication as part of Python wheels.
+set(CMAKE_PLATFORM_NO_VERSIONED_SONAME ON)
+
+
 # The directory at which the Python import tree begins.
 set(MPACT_PYTHON_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/mpact")
 
+# We vendor our own MLIR instance in the `mpact` namespace.
+add_compile_definitions("MLIR_PYTHON_PACKAGE_PREFIX=mpact.")
+
 declare_mlir_python_sources(MPACTPythonSources)
+declare_mlir_python_sources(MPACTPythonExtensions)
 
 declare_mlir_python_sources(MPACTPythonSources.PublicAPI
   ROOT_DIR "${MPACT_PYTHON_ROOT_DIR}"
   ADD_TO_PARENT MPACTPythonSources
   SOURCES
     mpactbackend.py
+  )
+
+#-------------------------------------------------------------------------------
+# Extensions
+#-------------------------------------------------------------------------------
+
+declare_mlir_python_extension(MPACTPythonExtensions.Main
+  MODULE_NAME _mpact
+  ADD_TO_PARENT MPACTPythonExtensions
+  SOURCES
+    MPACTModule.cpp
+  EMBED_CAPI_LINK_LIBS
+    MPACTCAPI
+  PRIVATE_LINK_LIBS
+    LLVMSupport
 )
 
 declare_mlir_python_sources(MPACTPythonSources.SampleModels
@@ -25,12 +49,36 @@
 # Python Modules
 #-------------------------------------------------------------------------------
 
+# To compiler TorchMLIRPythonExtension (which registers torch dialect and
+# related passes).
+# TODO: is there a cleaner way?
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../externals/torch-mlir/include)
+
 set(_source_components
+  MLIRPythonSources
+  MLIRPythonExtension.Core
+  MLIRPythonExtension.RegisterEverything
+
+  # We need the FxImporter from torch-mlir
+  TorchMLIRPythonSources.Importers
+  TorchMLIRPythonExtensions
+
   MPACTPythonSources
+  MPACTPythonExtensions
+)
+
+add_mlir_python_common_capi_library(MPACTAggregateCAPI
+  INSTALL_COMPONENT MPACTPythonModules
+  INSTALL_DESTINATION python_packages/mpact/mpact/_mlir_libs
+  OUTPUT_DIRECTORY "${MPACT_PYTHON_PACKAGES_DIR}/mpact/mpact/_mlir_libs"
+  RELATIVE_INSTALL_ROOT ".."
+  DECLARED_SOURCES ${_source_components}
 )
 
 add_mlir_python_modules(MPACTPythonModules
   ROOT_PREFIX "${MPACT_PYTHON_PACKAGES_DIR}/mpact/mpact"
   INSTALL_PREFIX "python_packages/mpact/mpact"
   DECLARED_SOURCES ${_source_components}
+  COMMON_CAPI_LINK_LIBS
+    MPACTAggregateCAPI
 )
diff --git a/python/MPACTModule.cpp b/python/MPACTModule.cpp
new file mode 100644
index 0000000..5751287
--- /dev/null
+++ b/python/MPACTModule.cpp
@@ -0,0 +1,17 @@
+//===-- MPACTModule.cpp ------------------------------------*- cpp -*-===//
+//
+// Part of the MPACT Project, 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
+// Also available under a BSD-style license. See LICENSE.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Bindings/Python/PybindAdaptors.h"
+#include "mpact-c/Registration.h"
+
+PYBIND11_MODULE(_mpact, m) {
+  mpactRegisterAllPasses();
+
+  m.doc() = "mpact main python extension";
+}
diff --git a/python/mpact/mpactbackend.py b/python/mpact/mpactbackend.py
index 6df6137..bb8cc3b 100644
--- a/python/mpact/mpactbackend.py
+++ b/python/mpact/mpactbackend.py
@@ -1,28 +1,135 @@
+# Initialize mpact python extension.
+import mpact._mlir_libs._mpact
+
+import abc
 import ctypes
+from enum import Enum
+from io import StringIO
 import numpy as np
 import os
+import sys
+import tempfile
 import torch
 
-from typing import Any, Callable, Optional, Tuple, Dict
+from typing import Any, Callable, Optional, Tuple, Dict, TypeVar, Union
 
-from torch_mlir import ir
-from torch_mlir.compiler_utils import run_pipeline_with_repro_report
-from torch_mlir.dialects import torch as torch_d
-from torch_mlir.execution_engine import *
-from torch_mlir.extras.fx_importer import FxImporter, SparsityMeta
-from torch_mlir.ir import *
-from torch_mlir.passmanager import *
-from torch_mlir.runtime import *
-
-from torch_mlir_e2e_test.linalg_on_tensors_backends.refbackend import (
-    LinalgOnTensorsBackend,
-)
+from mpact import ir
+from mpact.ir import Module
+from mpact.dialects import torch as torch_d
+from mpact.execution_engine import *
+from mpact.extras.fx_importer import FxImporter, SparsityMeta
+from mpact.ir import *
+from mpact.passmanager import *
+from mpact.runtime import *
 
 # One time set up of support library and optimization level.
 SUPPORT_LIB = os.getenv("SUPPORT_LIB", default=None)
 SHARED_LIBS = [] if SUPPORT_LIB is None else [SUPPORT_LIB]
 OPT_LEVEL = int(os.getenv("OPT_LEVEL", default=2))
 
+# A type shared between the result of `LinalgOnTensorsBackend.compile` and the
+# input to `LinalgOnTensorsBackend.load`. Each backend will likely have a
+# different definition of this type.
+CompiledArtifact = TypeVar("CompiledArtifact")
+
+# A wrapper around a backend-specific loaded program representation
+# that uniformly translates the `x.method(...)` interface expected of
+# Torch modules into appropriate lower-level operations.
+Invoker = TypeVar("Invoker")
+
+
+class LinalgOnTensorsBackend(abc.ABC):
+    """The interface to an linalg-on-tensors backend.
+
+    Backends are recommended to raise meaningful exceptions in case of error,
+    ideally with easy reproduction instructions.
+    """
+
+    @abc.abstractmethod
+    def compile(self, module: Module) -> CompiledArtifact:
+        """Compile the provided MLIR module into a compiled artifact.
+
+        The module adheres to the linalg-on-tensors backend contract
+        (see the VerifyLinalgOnTensorsBackendContract pass).
+
+        The compiled artifact can be any type, but must be correctly
+        interpreted by the `load` method.
+        """
+
+    @abc.abstractmethod
+    def load(self, artifact: CompiledArtifact) -> Invoker:
+        """Load the compiled artifact into a uniformly invokable form.
+
+        The compiled artifact is the result of a previous call to `compile`.
+
+        See the description of `Invoker` for the requirements on the returned
+        type.
+        """
+
+
+def get_module_name_for_debug_dump(module):
+    """Gets a name suitable for a debug dump.
+
+    The name is not guaranteed to be unique.
+    """
+    if not "torch.debug_module_name" in module.operation.attributes:
+        return "UnnammedModule"
+    return StringAttr(module.operation.attributes["torch.debug_module_name"]).value
+
+
+class MPACTCompilerError(Exception):
+    pass
+
+
+def run_pipeline_with_repro_report(
+    module, pipeline: str, description: str, enable_ir_printing: bool = False
+):
+    """Runs `pipeline` on `module`, with a nice repro report if it fails."""
+    module_name = get_module_name_for_debug_dump(module)
+    original_stderr = sys.stderr
+    try:
+        sys.stderr = StringIO()
+        asm_for_error_report = module.operation.get_asm(
+            large_elements_limit=10, enable_debug_info=True
+        )
+        # Lower module in place to make it ready for compiler backends.
+        with module.context as ctx:
+            pm = PassManager.parse(pipeline)
+            if enable_ir_printing:
+                ctx.enable_multithreading(False)
+                pm.enable_ir_printing()
+            pm.run(module.operation)
+    except Exception as e:
+        # TODO: More robust.
+        # - don't arbitrarily clutter up /tmp. When a test suite has many
+        #   tests, this can be a big disk cost (also, /tmp/ is frequently a
+        #   RAM fs, which increases worries about capacity).
+        # - don't have colliding filenames (hard to do without cluttering
+        #   up /tmp)
+        # - if we do have have colliding filenames, writes should at least
+        #   avoid being racy.
+        filename = os.path.join(tempfile.gettempdir(), module_name + ".mlir")
+        with open(filename, "w") as f:
+            f.write(asm_for_error_report)
+        debug_options = "-mlir-print-ir-after-all -mlir-disable-threading"
+        # Put something descriptive here even if description is empty.
+        description = description or f"{module_name} compile"
+
+        message = f"""\
+            {description} failed with the following diagnostics:
+            {sys.stderr.getvalue()}
+
+            python exception: {e}
+
+            The error can be reproduced with:
+            $ mpact-opt -pass-pipeline='{pipeline}' {filename}
+            Add '{debug_options}' to get the IR dump for debugging purpose.
+            """
+        trimmed_message = "\n".join([m.lstrip() for m in message.split("\n")])
+        raise MPACTCompilerError(trimmed_message) from None
+    finally:
+        sys.stderr = original_stderr
+
 
 def assert_arg_type_is_supported(ty):
     SUPPORTED = [
@@ -153,6 +260,10 @@
             "func.func(linalg-generalize-named-ops)",
             "func.func(linalg-fuse-elementwise-ops)",
             "convert-shape-to-std",
+            # Propagate sparse encodings before sparsifier mini-pipeline.
+            # TODO: the following pass currently contains no pattern. Will be
+            # added as needed.
+            "func.func(sparse-encoding-propagation)",
             # MLIR Sparsifier mini-pipeline.
             "sparse-assembler{direct-out}",
             "sparsification-and-bufferization",
diff --git a/test/lit.cfg.py b/test/lit.cfg.py
index 8dc4ba4..910acca 100644
--- a/test/lit.cfg.py
+++ b/test/lit.cfg.py
@@ -64,7 +64,7 @@
     config.mpact_obj_root,
 ]
 tools = [
-    "torch-mlir-opt",
+    "mpact-opt",
     ToolSubst("%PYTHON", config.python_executable, unresolved="ignore"),
 ]
 
@@ -74,7 +74,6 @@
     "PYTHONPATH",
     [
         os.path.join(config.mpact_obj_root, "python_packages/mpact"),
-        os.path.join(config.torch_mlir_obj_root, "python_packages/torch_mlir"),
     ],
     append_path=True,
 )