Refactoring the iree::vm on top of iree::rt.
The dedupes the interfaces that were factored out previously and moves a bit more common code for printing/disassembling to keep feature parity with the old interface.

PiperOrigin-RevId: 274841300
diff --git a/iree/hal/interpreter/BUILD b/iree/hal/interpreter/BUILD
index 97235f6..204eb0a 100644
--- a/iree/hal/interpreter/BUILD
+++ b/iree/hal/interpreter/BUILD
@@ -18,6 +18,7 @@
         "//iree/hal:executable",
         "//iree/hal:executable_cache",
         "//iree/hal:executable_format",
+        "//iree/rt",
     ],
 )
 
@@ -38,13 +39,13 @@
         "//iree/hal:allocator",
         "//iree/hal:buffer_view",
         "//iree/hal:heap_buffer",
+        "//iree/rt",
         "//iree/schemas/bytecode:interpreter_bytecode_v0",
+        "//iree/vm:bytecode_module",
         "//iree/vm:bytecode_reader",
         "//iree/vm:bytecode_tables_interpreter",
         "//iree/vm:bytecode_util",
-        "//iree/vm:function",
         "//iree/vm:opcode_info",
-        "//iree/vm:stack",
         "//iree/vm:type",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:inlined_vector",
@@ -57,16 +58,13 @@
     srcs = ["bytecode_executable.cc"],
     hdrs = ["bytecode_executable.h"],
     deps = [
-        ":interpreter_context",
+        ":interpreter_module",
         "//iree/base:status",
         "//iree/hal:allocator",
         "//iree/hal:executable",
         "//iree/hal:executable_spec",
+        "//iree/rt",
         "//iree/vm:bytecode_tables_interpreter",
-        "//iree/vm:bytecode_validator",
-        "//iree/vm:context",
-        "//iree/vm:module",
-        "//iree/vm:module_printer",
         "@com_google_absl//absl/types:span",
     ],
 )
@@ -116,30 +114,13 @@
         "//iree/base:tracing",
         "//iree/hal:buffer_view",
         "//iree/hal/host:host_local_command_processor",
+        "//iree/rt",
         "@com_google_absl//absl/container:inlined_vector",
         "@com_google_absl//absl/types:span",
     ],
 )
 
 cc_library(
-    name = "interpreter_context",
-    srcs = ["interpreter_context.cc"],
-    hdrs = ["interpreter_context.h"],
-    deps = [
-        ":bytecode_dispatch",
-        ":bytecode_kernels",
-        "//iree/base:flatbuffer_util",
-        "//iree/base:status",
-        "//iree/hal:allocator",
-        "//iree/hal:buffer_view",
-        "//iree/vm:context",
-        "//iree/vm:function",
-        "//iree/vm:stack",
-        "@com_google_absl//absl/types:span",
-    ],
-)
-
-cc_library(
     name = "interpreter_device",
     srcs = ["interpreter_device.cc"],
     hdrs = ["interpreter_device.h"],
@@ -159,6 +140,7 @@
         "//iree/hal/host:host_local_allocator",
         "//iree/hal/host:host_submission_queue",
         "//iree/hal/host:inproc_command_buffer",
+        "//iree/rt",
         "@com_google_absl//absl/container:inlined_vector",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/types:span",
@@ -187,3 +169,22 @@
     ],
     alwayslink = 1,
 )
+
+cc_library(
+    name = "interpreter_module",
+    srcs = ["interpreter_module.cc"],
+    hdrs = ["interpreter_module.h"],
+    deps = [
+        ":bytecode_dispatch",
+        ":bytecode_kernels",
+        "//iree/base:flatbuffer_util",
+        "//iree/base:status",
+        "//iree/base:tracing",
+        "//iree/hal:allocator",
+        "//iree/hal:buffer_view",
+        "//iree/rt",
+        "//iree/vm:bytecode_module",
+        "//iree/vm:bytecode_tables_interpreter",
+        "@com_google_absl//absl/types:span",
+    ],
+)
diff --git a/iree/hal/interpreter/CMakeLists.txt b/iree/hal/interpreter/CMakeLists.txt
index 5031688..5295105 100644
--- a/iree/hal/interpreter/CMakeLists.txt
+++ b/iree/hal/interpreter/CMakeLists.txt
@@ -75,7 +75,7 @@
     iree::hal::allocator
     iree::hal::executable
     iree::hal::executable_spec
-    iree::hal::interpreter::interpreter_context
+    iree::hal::interpreter::interpreter_module
     iree::vm::bytecode_tables_interpreter
     iree::vm::bytecode_validator
     iree::vm::context
@@ -138,27 +138,6 @@
 
 iree_cc_library(
   NAME
-    interpreter_context
-  HDRS
-    "interpreter_context.h"
-  SRCS
-    "interpreter_context.cc"
-  DEPS
-    absl::span
-    iree::base::flatbuffer_util
-    iree::base::status
-    iree::hal::allocator
-    iree::hal::buffer_view
-    iree::hal::interpreter::bytecode_dispatch
-    iree::hal::interpreter::bytecode_kernels
-    iree::vm::context
-    iree::vm::function
-    iree::vm::stack
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
     interpreter_device
   HDRS
     "interpreter_device.h"
@@ -212,3 +191,24 @@
     iree::hal::interpreter::interpreter_driver
   PUBLIC
 )
+
+iree_cc_library(
+  NAME
+    interpreter_module
+  HDRS
+    "interpreter_module.h"
+  SRCS
+    "interpreter_module.cc"
+  DEPS
+    absl::span
+    iree::base::flatbuffer_util
+    iree::base::status
+    iree::hal::allocator
+    iree::hal::buffer_view
+    iree::hal::interpreter::bytecode_dispatch
+    iree::hal::interpreter::bytecode_kernels
+    iree::vm::context
+    iree::vm::function
+    iree::vm::stack
+  PUBLIC
+)
diff --git a/iree/hal/interpreter/bytecode_cache.cc b/iree/hal/interpreter/bytecode_cache.cc
index de7d8f2..b42db5a 100644
--- a/iree/hal/interpreter/bytecode_cache.cc
+++ b/iree/hal/interpreter/bytecode_cache.cc
@@ -23,8 +23,9 @@
 namespace iree {
 namespace hal {
 
-BytecodeCache::BytecodeCache(hal::Allocator* allocator)
-    : allocator_(allocator) {}
+BytecodeCache::BytecodeCache(ref_ptr<rt::Instance> instance,
+                             hal::Allocator* allocator)
+    : instance_(std::move(instance)), allocator_(allocator) {}
 
 BytecodeCache::~BytecodeCache() = default;
 
@@ -43,9 +44,9 @@
   // Wrap the data (or copy it).
   bool allow_aliasing_data =
       AllBitsSet(mode, ExecutableCachingMode::kAliasProvidedData);
-  ASSIGN_OR_RETURN(
-      auto executable,
-      BytecodeExecutable::Load(allocator_, spec, !allow_aliasing_data));
+  ASSIGN_OR_RETURN(auto executable,
+                   BytecodeExecutable::Load(add_ref(instance_), allocator_,
+                                            spec, !allow_aliasing_data));
 
   return executable;
 }
diff --git a/iree/hal/interpreter/bytecode_cache.h b/iree/hal/interpreter/bytecode_cache.h
index fd955c5..4da59ec 100644
--- a/iree/hal/interpreter/bytecode_cache.h
+++ b/iree/hal/interpreter/bytecode_cache.h
@@ -18,13 +18,14 @@
 #include "iree/hal/allocator.h"
 #include "iree/hal/executable.h"
 #include "iree/hal/executable_cache.h"
+#include "iree/rt/instance.h"
 
 namespace iree {
 namespace hal {
 
 class BytecodeCache final : public ExecutableCache {
  public:
-  explicit BytecodeCache(hal::Allocator* allocator);
+  BytecodeCache(ref_ptr<rt::Instance> instance, hal::Allocator* allocator);
   ~BytecodeCache() override;
 
   bool CanPrepareFormat(ExecutableFormat format) const override;
@@ -33,6 +34,7 @@
       ExecutableCachingModeBitfield mode, const ExecutableSpec& spec) override;
 
  private:
+  ref_ptr<rt::Instance> instance_;
   hal::Allocator* allocator_;
 };
 
diff --git a/iree/hal/interpreter/bytecode_dispatch.cc b/iree/hal/interpreter/bytecode_dispatch.cc
index b89fb44..f0e7040 100644
--- a/iree/hal/interpreter/bytecode_dispatch.cc
+++ b/iree/hal/interpreter/bytecode_dispatch.cc
@@ -33,11 +33,12 @@
 #include "iree/hal/interpreter/bytecode_dispatch_conversion.h"
 #include "iree/hal/interpreter/bytecode_dispatch_util.h"
 #include "iree/hal/interpreter/bytecode_kernels.h"
+#include "iree/rt/function.h"
 #include "iree/schemas/bytecode/interpreter_bytecode_v0.h"
+#include "iree/vm/bytecode_module.h"
 #include "iree/vm/bytecode_reader.h"
 #include "iree/vm/bytecode_tables_interpreter.h"
 #include "iree/vm/bytecode_util.h"
-#include "iree/vm/function.h"
 #include "iree/vm/opcode_info.h"
 
 namespace iree {
@@ -45,11 +46,9 @@
 
 namespace {
 
+using ::iree::rt::Stack;
+using ::iree::rt::StackFrame;
 using ::iree::vm::BytecodeReader;
-using ::iree::vm::ImportFunction;
-using ::iree::vm::NativeFunction;
-using ::iree::vm::Stack;
-using ::iree::vm::StackFrame;
 
 }  // namespace
 
@@ -111,38 +110,23 @@
   DISPATCH_CORE_OPCODE(kCall, {
     auto* old_stack_frame = stack->current_frame();
     ASSIGN_OR_RETURN(const auto& target_function, reader.ReadFunction());
+    // TODO(benvanik): rework register storage interface.
+    ASSIGN_OR_RETURN(
+        const auto* function_def,
+        static_cast<const vm::BytecodeModule*>(target_function.module())
+            ->GetFunctionDef(target_function.linkage(),
+                             target_function.ordinal()));
     ASSIGN_OR_RETURN(auto* new_stack_frame, stack->PushFrame(target_function));
+    new_stack_frame->mutable_registers()->buffer_views.resize(
+        function_def->bytecode()->local_count());
     RETURN_IF_ERROR(
         reader.CopyInputsAndSwitchStackFrame(old_stack_frame, new_stack_frame));
     DVLOG(1) << "Call; stack now: " << stack->DebugString();
   });
 
   DISPATCH_CORE_OPCODE(kCallImport, {
-    auto* old_stack_frame = stack->current_frame();
-    ASSIGN_OR_RETURN(const auto* target_function, reader.ReadImportFunction());
-    switch (target_function->link_type()) {
-      case ImportFunction::LinkType::kModule: {
-        ASSIGN_OR_RETURN(auto* new_stack_frame,
-                         stack->PushFrame(target_function->linked_function()));
-        RETURN_IF_ERROR(reader.CopyInputsAndSwitchStackFrame(old_stack_frame,
-                                                             new_stack_frame));
-        DVLOG(1) << "Call module import; stack now: " << stack->DebugString();
-        break;
-      }
-      case ImportFunction::LinkType::kNativeFunction: {
-        ASSIGN_OR_RETURN(auto* new_stack_frame,
-                         stack->PushFrame(*target_function));
-        RETURN_IF_ERROR(reader.CopyInputsAndSwitchStackFrame(old_stack_frame,
-                                                             new_stack_frame));
-        DVLOG(1) << "Call native import; stack now: " << stack->DebugString();
-        RETURN_IF_ERROR(CallNativeFunction(stack, *target_function));
-        RETURN_IF_ERROR(reader.CopyResultsAndSwitchStackFrame(old_stack_frame,
-                                                              new_stack_frame));
-        RETURN_IF_ERROR(stack->PopFrame());
-        DVLOG(1) << "Return from native; stack now: " << stack->DebugString();
-        break;
-      }
-    }
+    return UnimplementedErrorBuilder(IREE_LOC)
+           << "Non-module imports not supported";
   });
 
   DISPATCH_CORE_OPCODE(kCallIndirect, {
@@ -156,8 +140,9 @@
       // Returning from entry function. Marshal results from the return stmt.
       ASSIGN_OR_RETURN(int32_t src_count, reader.ReadCount());
       for (int i = 0; i < src_count; ++i) {
-        ASSIGN_OR_RETURN(auto* src_local,
-                         reader.ReadLocal(old_stack_frame->mutable_locals()));
+        ASSIGN_OR_RETURN(
+            auto* src_local,
+            reader.ReadLocal(old_stack_frame->mutable_registers()));
         entry_results[i] = std::move(*src_local);
       }
       DVLOG(1) << "Returning to entry";
diff --git a/iree/hal/interpreter/bytecode_dispatch.h b/iree/hal/interpreter/bytecode_dispatch.h
index 1d4b7d1..edd92d2 100644
--- a/iree/hal/interpreter/bytecode_dispatch.h
+++ b/iree/hal/interpreter/bytecode_dispatch.h
@@ -18,15 +18,15 @@
 #include "iree/base/status.h"
 #include "iree/hal/allocator.h"
 #include "iree/hal/interpreter/bytecode_kernels.h"
-#include "iree/vm/stack.h"
-#include "iree/vm/stack_frame.h"
+#include "iree/rt/stack.h"
+#include "iree/rt/stack_frame.h"
 
 namespace iree {
 namespace hal {
 
 Status Dispatch(hal::Allocator* allocator,
-                kernels::RuntimeState* kernel_runtime_state, vm::Stack* stack,
-                vm::StackFrame* entry_stack_frame,
+                kernels::RuntimeState* kernel_runtime_state, rt::Stack* stack,
+                rt::StackFrame* entry_stack_frame,
                 absl::Span<BufferView> entry_results);
 
 }  // namespace hal
diff --git a/iree/hal/interpreter/bytecode_dispatch_util.cc b/iree/hal/interpreter/bytecode_dispatch_util.cc
index 0e59bd8..40937e1 100644
--- a/iree/hal/interpreter/bytecode_dispatch_util.cc
+++ b/iree/hal/interpreter/bytecode_dispatch_util.cc
@@ -34,18 +34,6 @@
   return false;
 }
 
-Status CallNativeFunction(vm::Stack* stack,
-                          const vm::ImportFunction& function) {
-  auto* stack_frame = stack->current_frame();
-
-  // Marshal inputs and outputs.
-  auto args = stack_frame->mutable_locals().subspan(0, function.input_count());
-  auto results = stack_frame->mutable_locals().subspan(args.size());
-
-  const auto& fn = function.native_function();
-  return fn(stack, args, results);
-}
-
 Status ValidateElementwiseUnaryOp(BufferView* src_local,
                                   BufferView* dst_local) {
   // TODO(benvanik): validate shapes.
diff --git a/iree/hal/interpreter/bytecode_dispatch_util.h b/iree/hal/interpreter/bytecode_dispatch_util.h
index b8f40e1..cb8d7d9 100644
--- a/iree/hal/interpreter/bytecode_dispatch_util.h
+++ b/iree/hal/interpreter/bytecode_dispatch_util.h
@@ -24,10 +24,10 @@
 #include "iree/hal/buffer_view.h"
 #include "iree/hal/heap_buffer.h"
 #include "iree/hal/interpreter/bytecode_kernels.h"
+#include "iree/rt/function.h"
+#include "iree/rt/stack.h"
 #include "iree/schemas/bytecode/interpreter_bytecode_v0.h"
 #include "iree/vm/bytecode_reader.h"
-#include "iree/vm/function.h"
-#include "iree/vm/stack.h"
 #include "iree/vm/type.h"
 
 // TODO(benvanik): move to dedicated config file/build flags.
@@ -42,8 +42,6 @@
 // bitwise zero.
 bool BufferViewIsTrue(const BufferView& buffer_view);
 
-Status CallNativeFunction(vm::Stack* stack, const vm::ImportFunction& function);
-
 Status ValidateElementwiseUnaryOp(BufferView* src_local, BufferView* dst_local);
 Status ValidateElementwiseBinaryOp(BufferView* lhs_local, BufferView* rhs_local,
                                    BufferView* dst_local);
diff --git a/iree/hal/interpreter/bytecode_executable.cc b/iree/hal/interpreter/bytecode_executable.cc
index 130eb77..c528263 100644
--- a/iree/hal/interpreter/bytecode_executable.cc
+++ b/iree/hal/interpreter/bytecode_executable.cc
@@ -16,63 +16,40 @@
 
 #include <iostream>
 
-#include "iree/vm/bytecode_tables_interpreter.h"
-#include "iree/vm/bytecode_validator.h"
-#include "iree/vm/module.h"
-#include "iree/vm/module_printer.h"
+#include "iree/hal/interpreter/interpreter_module.h"
+#include "iree/rt/policy.h"
 
 namespace iree {
 namespace hal {
 
-namespace {
-// TODO(benvanik): remove when debugger is wired up to the HAL.
-const bool kEnableExecutablePrinting = false;
-}  // namespace
-
 // static
 StatusOr<ref_ptr<BytecodeExecutable>> BytecodeExecutable::Load(
-    hal::Allocator* allocator, ExecutableSpec spec, bool allow_aliasing_data) {
+    ref_ptr<rt::Instance> instance, hal::Allocator* allocator,
+    ExecutableSpec spec, bool allow_aliasing_data) {
   // Allocate the executable now.
   // We do this here so that if we need to clone the data we are passing that
   // to the VM loader instead of the data we may not have access to later.
-  auto executable =
-      make_ref<BytecodeExecutable>(allocator, spec, allow_aliasing_data);
-  auto* context = executable->mutable_context();
+  auto executable = make_ref<BytecodeExecutable>(std::move(instance), allocator,
+                                                 spec, allow_aliasing_data);
 
   // Create the executable module.
   auto module_def =
       ::flatbuffers::GetRoot<ModuleDef>(executable->executable_data().data());
-  ASSIGN_OR_RETURN(auto module, vm::Module::FromDef(*module_def));
-  executable->module_ = module.get();
-  RETURN_IF_ERROR(context->RegisterModule(std::move(module)));
-
-  // Validate bytecode to ensure it will be usable for execution.
-  // We do this here so that we get a good stack immediately when the bytecode
-  // is provided instead of when we go to run it. This more closely mirrors how
-  // a backend that performed compilation (such as SPIR-V) would fail.
-  for (auto* function_def :
-       *executable->module().function_table().def().functions()) {
-    RETURN_IF_ERROR(vm::BytecodeValidator::Validate(
-        *context, executable->module(), *function_def->bytecode()));
-  }
-
-  // Print the bytecode.
-  // TODO(benvanik): remove when debugger is wired up to the HAL.
-  if (kEnableExecutablePrinting) {
-    vm::PrintModuleFlagBitfield print_flags = vm::PrintModuleFlag::kNone;
-    for (const auto& module : context->modules()) {
-      RETURN_IF_ERROR(vm::PrintModuleToStream(
-          vm::interpreter_opcode_table(), *module, print_flags, &std::cout));
-    }
-  }
+  ASSIGN_OR_RETURN(auto module,
+                   InterpreterModule::FromDef(allocator, *module_def));
+  executable->module_ = add_ref(module);
+  RETURN_IF_ERROR(executable->context()->RegisterModule(std::move(module)));
 
   return executable;
 }
 
-BytecodeExecutable::BytecodeExecutable(hal::Allocator* allocator,
+BytecodeExecutable::BytecodeExecutable(ref_ptr<rt::Instance> instance,
+                                       hal::Allocator* allocator,
                                        ExecutableSpec spec,
                                        bool allow_aliasing_data)
-    : spec_(spec), context_(allocator) {
+    : spec_(spec),
+      context_(
+          make_ref<rt::Context>(std::move(instance), make_ref<rt::Policy>())) {
   if (!allow_aliasing_data) {
     // Clone data.
     cloned_executable_data_ = {spec.executable_data.begin(),
diff --git a/iree/hal/interpreter/bytecode_executable.h b/iree/hal/interpreter/bytecode_executable.h
index 1fae48f..6cd47de 100644
--- a/iree/hal/interpreter/bytecode_executable.h
+++ b/iree/hal/interpreter/bytecode_executable.h
@@ -22,20 +22,21 @@
 #include "iree/hal/allocator.h"
 #include "iree/hal/executable.h"
 #include "iree/hal/executable_spec.h"
-#include "iree/hal/interpreter/interpreter_context.h"
-#include "iree/vm/context.h"
+#include "iree/rt/context.h"
+#include "iree/rt/instance.h"
+#include "iree/rt/module.h"
 
 namespace iree {
 namespace hal {
 
 class BytecodeExecutable final : public Executable {
  public:
-  static StatusOr<ref_ptr<BytecodeExecutable>> Load(hal::Allocator* allocator,
-                                                    ExecutableSpec spec,
-                                                    bool allow_aliasing_data);
+  static StatusOr<ref_ptr<BytecodeExecutable>> Load(
+      ref_ptr<rt::Instance> instance, hal::Allocator* allocator,
+      ExecutableSpec spec, bool allow_aliasing_data);
 
-  BytecodeExecutable(hal::Allocator* allocator, ExecutableSpec spec,
-                     bool allow_aliasing_data);
+  BytecodeExecutable(ref_ptr<rt::Instance> instance, hal::Allocator* allocator,
+                     ExecutableSpec spec, bool allow_aliasing_data);
   ~BytecodeExecutable() override;
 
   bool supports_debugging() const override { return false; }
@@ -46,20 +47,19 @@
   }
 
   // VM context with the executable registered.
-  const InterpreterContext& context() const { return context_; }
-  InterpreterContext* mutable_context() { return &context_; }
+  const ref_ptr<rt::Context>& context() const { return context_; }
 
   // VM module representing the executable.
   // Note that there may be more than one module in the Context and only this
   // module can be used to lookup executable exports.
-  const vm::Module& module() const { return *module_; }
+  const ref_ptr<rt::Module>& module() const { return module_; }
 
  private:
   ExecutableSpec spec_;
   std::vector<uint8_t> cloned_executable_data_;
 
-  InterpreterContext context_;
-  vm::Module* module_ = nullptr;
+  ref_ptr<rt::Context> context_;
+  ref_ptr<rt::Module> module_;
 };
 
 }  // namespace hal
diff --git a/iree/hal/interpreter/interpreter_command_processor.cc b/iree/hal/interpreter/interpreter_command_processor.cc
index a79b10e..783809d 100644
--- a/iree/hal/interpreter/interpreter_command_processor.cc
+++ b/iree/hal/interpreter/interpreter_command_processor.cc
@@ -21,6 +21,7 @@
 #include "iree/base/tracing.h"
 #include "iree/hal/buffer_view.h"
 #include "iree/hal/interpreter/bytecode_executable.h"
+#include "iree/rt/stack.h"
 
 namespace iree {
 namespace hal {
@@ -40,26 +41,23 @@
   auto* executable =
       static_cast<BytecodeExecutable*>(dispatch_request.executable);
   const auto& module = executable->module();
-  ASSIGN_OR_RETURN(auto entry_function, module.function_table().LookupExport(
+  ASSIGN_OR_RETURN(auto entry_function, module->LookupFunctionByOrdinal(
+                                            rt::Function::Linkage::kExport,
                                             dispatch_request.entry_point));
 
-  vm::Stack stack;
+  rt::Stack stack(executable->context().get());
 
   // TODO(benvanik): avoid this by directly referencing the bindings.
-  absl::InlinedVector<BufferView, 8> args;
-  args.reserve(dispatch_request.bindings.size());
+  absl::InlinedVector<BufferView, 8> arguments;
+  arguments.reserve(dispatch_request.bindings.size());
   for (auto& binding : dispatch_request.bindings) {
-    args.push_back(BufferView{add_ref(binding.buffer), binding.shape,
-                              binding.element_size});
+    arguments.push_back(BufferView{add_ref(binding.buffer), binding.shape,
+                                   binding.element_size});
   }
   absl::InlinedVector<BufferView, 8> results;
-  if (entry_function.result_count() > 0) {
-    return UnimplementedErrorBuilder(IREE_LOC)
-           << "Executable export results are not yet implemented";
-  }
 
-  RETURN_IF_ERROR(executable->context().Invoke(
-      &stack, entry_function, absl::MakeSpan(args), absl::MakeSpan(results)));
+  RETURN_IF_ERROR(executable->module()->Execute(
+      &stack, entry_function, std::move(arguments), &results));
 
   return OkStatus();
 }
diff --git a/iree/hal/interpreter/interpreter_context.cc b/iree/hal/interpreter/interpreter_context.cc
deleted file mode 100644
index e30aa96..0000000
--- a/iree/hal/interpreter/interpreter_context.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/hal/interpreter/interpreter_context.h"
-
-#include "iree/base/flatbuffer_util.h"
-#include "iree/base/status.h"
-#include "iree/hal/interpreter/bytecode_dispatch.h"
-
-namespace iree {
-namespace hal {
-
-namespace {
-
-using ::iree::vm::Function;
-
-}  // namespace
-
-Status InterpreterContext::Invoke(vm::Stack* stack, Function function,
-                                  absl::Span<BufferView> args,
-                                  absl::Span<BufferView> results) const {
-  // Verify arg/result counts.
-  if (args.size() != function.input_count()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Function " << function.name() << " requires "
-           << function.input_count() << " inputs but only " << args.size()
-           << " provided";
-  }
-  if (results.size() != function.result_count()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Function " << function.name() << " requires "
-           << function.result_count() << " outputs but only " << results.size()
-           << " provided";
-  }
-
-  // Push stack frame for the function we are calling.
-  ASSIGN_OR_RETURN(auto* callee_stack_frame, stack->PushFrame(function));
-
-  // Marshal input arguments.
-  for (int i = 0; i < args.size(); ++i) {
-    *callee_stack_frame->mutable_local(i) = std::move(args[i]);
-  }
-
-  // Run main dispatch loop until it exits (or errors).
-  RETURN_IF_ERROR(Dispatch(allocator_, &kernel_runtime_state_, stack,
-                           callee_stack_frame, results));
-
-  // Pop the callee frame to balance out the stack.
-  RETURN_IF_ERROR(stack->PopFrame());
-
-  return OkStatus();
-}
-
-}  // namespace hal
-}  // namespace iree
diff --git a/iree/hal/interpreter/interpreter_context.h b/iree/hal/interpreter/interpreter_context.h
deleted file mode 100644
index 2808c14..0000000
--- a/iree/hal/interpreter/interpreter_context.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_HAL_INTERPRETER_INTERPRETER_CONTEXT_H_
-#define IREE_HAL_INTERPRETER_INTERPRETER_CONTEXT_H_
-
-#include <memory>
-
-#include "absl/types/span.h"
-#include "iree/base/status.h"
-#include "iree/hal/allocator.h"
-#include "iree/hal/buffer_view.h"
-#include "iree/hal/interpreter/bytecode_kernels.h"
-#include "iree/vm/context.h"
-#include "iree/vm/function.h"
-#include "iree/vm/stack.h"
-
-namespace iree {
-namespace hal {
-
-class InterpreterContext final : public vm::Context {
- public:
-  explicit InterpreterContext(hal::Allocator* allocator)
-      : allocator_(allocator) {}
-
-  // TODO(benvanik): helpers to make passing args easier
-  Status Invoke(vm::Stack* stack, vm::Function function,
-                absl::Span<BufferView> args,
-                absl::Span<BufferView> results) const;
-
- private:
-  hal::Allocator* allocator_;
-  mutable kernels::RuntimeState kernel_runtime_state_;
-};
-
-}  // namespace hal
-}  // namespace iree
-
-#endif  // IREE_HAL_INTERPRETER_INTERPRETER_CONTEXT_H_
diff --git a/iree/hal/interpreter/interpreter_device.cc b/iree/hal/interpreter/interpreter_device.cc
index 239084e..7a61cc3 100644
--- a/iree/hal/interpreter/interpreter_device.cc
+++ b/iree/hal/interpreter/interpreter_device.cc
@@ -97,11 +97,12 @@
 }  // namespace
 
 InterpreterDevice::InterpreterDevice(DeviceInfo device_info)
-    : Device(std::move(device_info)) {
+    : Device(std::move(device_info)), instance_(make_ref<rt::Instance>()) {
   // We currently only expose a single command queue.
   auto command_queue = absl::make_unique<UnsynchronizedCommandQueue>(
       &allocator_, "cpu0",
       CommandCategory::kTransfer | CommandCategory::kDispatch);
+
   // TODO(benvanik): allow injection of the wrapper type to support
   // SyncCommandQueue without always linking in both.
   auto async_command_queue =
@@ -112,7 +113,7 @@
 InterpreterDevice::~InterpreterDevice() = default;
 
 std::shared_ptr<ExecutableCache> InterpreterDevice::CreateExecutableCache() {
-  return std::make_shared<BytecodeCache>(&allocator_);
+  return std::make_shared<BytecodeCache>(add_ref(instance_), &allocator_);
 }
 
 StatusOr<ref_ptr<CommandBuffer>> InterpreterDevice::CreateCommandBuffer(
diff --git a/iree/hal/interpreter/interpreter_device.h b/iree/hal/interpreter/interpreter_device.h
index 9afdaac..5987ab9 100644
--- a/iree/hal/interpreter/interpreter_device.h
+++ b/iree/hal/interpreter/interpreter_device.h
@@ -21,6 +21,7 @@
 #include "iree/hal/device.h"
 #include "iree/hal/host/host_local_allocator.h"
 #include "iree/hal/interpreter/bytecode_kernels.h"
+#include "iree/rt/instance.h"
 
 namespace iree {
 namespace hal {
@@ -66,6 +67,7 @@
   Status WaitIdle(absl::Time deadline) override;
 
  private:
+  ref_ptr<rt::Instance> instance_;
   kernels::RuntimeState kernel_runtime_state_;
   mutable HostLocalAllocator allocator_;
   mutable absl::InlinedVector<std::unique_ptr<CommandQueue>, 1> command_queues_;
diff --git a/iree/hal/interpreter/interpreter_module.cc b/iree/hal/interpreter/interpreter_module.cc
new file mode 100644
index 0000000..c569fea
--- /dev/null
+++ b/iree/hal/interpreter/interpreter_module.cc
@@ -0,0 +1,80 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "iree/hal/interpreter/interpreter_module.h"
+
+#include "iree/base/flatbuffer_util.h"
+#include "iree/base/status.h"
+#include "iree/base/tracing.h"
+#include "iree/hal/interpreter/bytecode_dispatch.h"
+#include "iree/vm/bytecode_tables_interpreter.h"
+
+namespace iree {
+namespace hal {
+
+// static
+StatusOr<ref_ptr<rt::Module>> InterpreterModule::FromDef(
+    hal::Allocator* allocator, const ModuleDef& module_def) {
+  ASSIGN_OR_RETURN(auto module_file,
+                   vm::ModuleFile::Create(&module_def, []() {}));
+  if (module_file->root() == nullptr) {
+    return InvalidArgumentErrorBuilder(IREE_LOC) << "No root ModuleDef present";
+  }
+
+  auto module =
+      assign_ref(new InterpreterModule(allocator, std::move(module_file)));
+
+  // TODO(benvanik): validate internals here? or make explicit?
+
+  return {std::move(module)};
+}
+
+InterpreterModule::InterpreterModule(
+    hal::Allocator* allocator, std::unique_ptr<vm::ModuleFile> module_file)
+    : vm::BytecodeModule(std::move(module_file),
+                         vm::interpreter_opcode_table()),
+      allocator_(allocator) {}
+
+Status InterpreterModule::Execute(
+    rt::Stack* stack, const rt::Function function,
+    absl::InlinedVector<hal::BufferView, 8> arguments,
+    absl::InlinedVector<hal::BufferView, 8>* results) const {
+  IREE_TRACE_SCOPE0("InterperterModule::Execute");
+
+  // Push stack frame for the function we are calling.
+  ASSIGN_OR_RETURN(auto* callee_stack_frame, stack->PushFrame(function));
+
+  // TODO(benvanik): rework register storage interface.
+  ASSIGN_OR_RETURN(const auto* function_def,
+                   GetFunctionDef(function.linkage(), function.ordinal()));
+  auto* registers = callee_stack_frame->mutable_registers();
+  registers->buffer_views.resize(function_def->bytecode()->local_count());
+
+  // Marshal input arguments.
+  for (int i = 0; i < arguments.size(); ++i) {
+    registers->buffer_views[i] = std::move(arguments[i]);
+  }
+
+  // Run main dispatch loop until it exits (or errors).
+  RETURN_IF_ERROR(Dispatch(allocator_, &kernel_runtime_state_, stack,
+                           callee_stack_frame, results));
+
+  // Pop the callee frame to balance out the stack.
+  RETURN_IF_ERROR(stack->PopFrame());
+
+  return OkStatus();
+}
+
+}  // namespace hal
+}  // namespace iree
diff --git a/iree/hal/interpreter/interpreter_module.h b/iree/hal/interpreter/interpreter_module.h
new file mode 100644
index 0000000..d7d98e3
--- /dev/null
+++ b/iree/hal/interpreter/interpreter_module.h
@@ -0,0 +1,55 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_HAL_INTERPRETER_INTERPRETER_MODULE_H_
+#define IREE_HAL_INTERPRETER_INTERPRETER_MODULE_H_
+
+#include <memory>
+
+#include "absl/types/span.h"
+#include "iree/base/status.h"
+#include "iree/hal/allocator.h"
+#include "iree/hal/buffer_view.h"
+#include "iree/hal/interpreter/bytecode_kernels.h"
+#include "iree/rt/function.h"
+#include "iree/rt/module.h"
+#include "iree/rt/stack.h"
+#include "iree/vm/bytecode_module.h"
+#include "iree/vm/bytecode_tables_interpreter.h"
+
+namespace iree {
+namespace hal {
+
+class InterpreterModule final : public vm::BytecodeModule {
+ public:
+  static StatusOr<ref_ptr<rt::Module>> FromDef(hal::Allocator* allocator,
+                                               const ModuleDef& module_def);
+
+  Status Execute(
+      rt::Stack* stack, const rt::Function function,
+      absl::InlinedVector<hal::BufferView, 8> arguments,
+      absl::InlinedVector<hal::BufferView, 8>* results) const override;
+
+ private:
+  InterpreterModule(hal::Allocator* allocator,
+                    std::unique_ptr<vm::ModuleFile> module_file);
+
+  hal::Allocator* allocator_;
+  mutable kernels::RuntimeState kernel_runtime_state_;
+};
+
+}  // namespace hal
+}  // namespace iree
+
+#endif  // IREE_HAL_INTERPRETER_INTERPRETER_MODULE_H_
diff --git a/iree/rt/BUILD b/iree/rt/BUILD
index 8fc7242..65e956b 100644
--- a/iree/rt/BUILD
+++ b/iree/rt/BUILD
@@ -39,25 +39,31 @@
         "function.cc",
         "instance.cc",
         "invocation.cc",
+        "module_printer.cc",
         "source_location.cc",
+        "stack.cc",
         "stack_frame.cc",
         "stack_trace.cc",
     ],
     hdrs = [
         "context.h",
+        "disassembler.h",
         "function.h",
         "function_signature.h",
         "instance.h",
         "invocation.h",
         "module.h",
+        "module_printer.h",
         "module_signature.h",
         "policy.h",
         "source_location.h",
         "source_resolver.h",
+        "stack.h",
         "stack_frame.h",
         "stack_trace.h",
     ],
     deps = [
+        "//iree/base:bitfield",
         "//iree/base:intrusive_list",
         "//iree/base:ref_ptr",
         "//iree/base:status",
diff --git a/iree/rt/CMakeLists.txt b/iree/rt/CMakeLists.txt
index 8cf7786..61b14a3 100644
--- a/iree/rt/CMakeLists.txt
+++ b/iree/rt/CMakeLists.txt
@@ -40,20 +40,25 @@
     "function.cc"
     "instance.cc"
     "invocation.cc"
+    "module_printer.cc"
     "source_location.cc"
+    "stack.cc"
     "stack_frame.cc"
     "stack_trace.cc"
   HDRS
     "context.h"
+    "disassembler.h"
     "function.h"
     "function_signature.h"
     "instance.h"
     "invocation.h"
     "module.h"
+    "module_printer.h"
     "module_signature.h"
     "policy.h"
     "source_location.h"
     "source_resolver.h"
+    "stack.h"
     "stack_frame.h"
     "stack_trace.h"
   DEPS
@@ -64,6 +69,7 @@
     absl::time
     absl::optional
     absl::span
+    iree::base::bitfield
     iree::base::intrusive_list
     iree::base::ref_ptr
     iree::base::status
diff --git a/iree/rt/api.cc b/iree/rt/api.cc
index 6e1e5c5..db43358 100644
--- a/iree/rt/api.cc
+++ b/iree/rt/api.cc
@@ -108,6 +108,8 @@
 
   SourceResolver* source_resolver() const override { return nullptr; }
 
+  Disassembler* disassembler() const override { return nullptr; }
+
   std::string DebugStringShort() const override { return std::string(name()); }
 
   StatusOr<const Function> LookupFunctionByOrdinal(
@@ -165,7 +167,7 @@
   }
 
   Status Execute(
-      const Function function,
+      Stack* stack, const Function function,
       absl::InlinedVector<hal::BufferView, 8> arguments,
       absl::InlinedVector<hal::BufferView, 8>* results) const override {
     // TODO(benvanik): fn ptr callback to external code. Waiting on fibers.
diff --git a/iree/rt/context.cc b/iree/rt/context.cc
index ba49cc4..6320325 100644
--- a/iree/rt/context.cc
+++ b/iree/rt/context.cc
@@ -123,6 +123,24 @@
                                       function_name);
 }
 
+StatusOr<const Function> Context::ResolveImport(const Module* module,
+                                                int32_t ordinal) const {
+  for (const auto& import_table_ref : module_import_tables_) {
+    if (import_table_ref.first == module) {
+      const auto& import_table = import_table_ref.second;
+      if (ordinal >= import_table.size()) {
+        return NotFoundErrorBuilder(IREE_LOC)
+               << "Import ordinal " << ordinal
+               << " out of bounds of import table (" << import_table.size()
+               << ")";
+      }
+      return import_table[ordinal];
+    }
+  }
+  return NotFoundErrorBuilder(IREE_LOC)
+         << "Import ordinal " << ordinal << " not found";
+}
+
 void Context::RegisterInvocation(Invocation* invocation) {
   {
     absl::MutexLock lock(&invocations_mutex_);
diff --git a/iree/rt/context.h b/iree/rt/context.h
index 83b7ee4..d67b79a 100644
--- a/iree/rt/context.h
+++ b/iree/rt/context.h
@@ -80,6 +80,11 @@
   // reference is valid for the lifetime of the context.
   StatusOr<const Function> ResolveFunction(absl::string_view full_name) const;
 
+  // Resolves an imported function by import ordinal. The function reference is
+  // valid for the lifetime of the context.
+  StatusOr<const Function> ResolveImport(const Module* module,
+                                         int32_t ordinal) const;
+
  private:
   // Resolves imports for the given module.
   StatusOr<ModuleImportTable> ResolveImports(Module* module);
diff --git a/iree/rt/disassembler.h b/iree/rt/disassembler.h
new file mode 100644
index 0000000..52d91ee
--- /dev/null
+++ b/iree/rt/disassembler.h
@@ -0,0 +1,64 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_RT_DISASSEMBLER_H_
+#define IREE_RT_DISASSEMBLER_H_
+
+#include <cstdint>
+#include <ostream>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "iree/base/status.h"
+#include "iree/rt/function.h"
+#include "iree/rt/source_location.h"
+
+namespace iree {
+namespace rt {
+
+// A single disassembled instruction.
+struct Instruction {
+  // Offset of the instruction within the function.
+  // The meaning of this is backend-dependent.
+  SourceOffset offset;
+
+  // The first line of |long_text|.
+  absl::string_view short_text;
+
+  // Human-readable text of the instruction. May contain multiple lines.
+  std::string long_text;
+};
+
+// Disassembles functions into instructions.
+//
+// Thread-safe.
+class Disassembler {
+ public:
+  virtual ~Disassembler() = default;
+
+  // Disassembles one or more instructions within the given function based on
+  // source offsets.
+  virtual StatusOr<std::vector<Instruction>> DisassembleInstructions(
+      const Function& function, SourceOffset offset,
+      int32_t instruction_count = INT32_MAX) const = 0;
+
+ protected:
+  Disassembler() = default;
+};
+
+}  // namespace rt
+}  // namespace iree
+
+#endif  // IREE_RT_DISASSEMBLER_H_
diff --git a/iree/rt/instance.h b/iree/rt/instance.h
index f5e3bc8..61fd544 100644
--- a/iree/rt/instance.h
+++ b/iree/rt/instance.h
@@ -21,14 +21,7 @@
 #include "iree/base/ref_ptr.h"
 #include "iree/hal/device_manager.h"
 #include "iree/rt/context.h"
-
-namespace iree {
-namespace rt {
-namespace debug {
-class DebugServer;
-}  // namespace debug
-}  // namespace rt
-}  // namespace iree
+#include "iree/rt/debug/debug_server.h"
 
 namespace iree {
 namespace rt {
@@ -46,7 +39,8 @@
 class Instance final : public RefObject<Instance> {
  public:
   // Creates an instance with an optional attached |debug_server|.
-  explicit Instance(std::unique_ptr<debug::DebugServer> debug_server = nullptr);
+  Instance() : Instance(nullptr) {}
+  explicit Instance(std::unique_ptr<debug::DebugServer> debug_server);
   ~Instance();
   Instance(const Instance&) = delete;
   Instance& operator=(const Instance&) = delete;
diff --git a/iree/rt/invocation.cc b/iree/rt/invocation.cc
index f35671e..0abe667 100644
--- a/iree/rt/invocation.cc
+++ b/iree/rt/invocation.cc
@@ -72,7 +72,7 @@
 
   // TODO(benvanik): fiber scheduling and such.
   auto execute_status = function.module()->Execute(
-      function, std::move(arguments), &results_value);
+      &invocation->stack_, function, std::move(arguments), &results_value);
   if (execute_status.ok()) {
     invocation->CompleteSuccess(std::move(results_value));
   } else {
@@ -82,12 +82,33 @@
   return invocation;
 }
 
+// static
+StatusOr<ref_ptr<Invocation>> Invocation::Create(
+    ref_ptr<Context> context, const Function function, ref_ptr<Policy> policy,
+    absl::Span<const ref_ptr<Invocation>> dependencies,
+    absl::Span<const hal::BufferView> arguments) {
+  absl::InlinedVector<ref_ptr<Invocation>, 4> dependency_list;
+  dependency_list.reserve(dependencies.size());
+  for (auto& dependency : dependencies) {
+    dependency_list.push_back(add_ref(dependency));
+  }
+  absl::InlinedVector<hal::BufferView, 8> argument_list;
+  argument_list.reserve(arguments.size());
+  for (auto& buffer_view : arguments) {
+    argument_list.push_back(buffer_view);
+  }
+  return Invocation::Create(std::move(context), function, std::move(policy),
+                            std::move(dependency_list),
+                            std::move(argument_list));
+}
+
 Invocation::Invocation(ref_ptr<Context> context, const Function function,
                        ref_ptr<Policy> policy)
     : id_(NextUniqueInvocationId()),
       context_(std::move(context)),
       function_(function),
-      policy_(std::move(policy)) {
+      policy_(std::move(policy)),
+      stack_(context_.get()) {
   IREE_TRACE_SCOPE0("Invocation::ctor");
   context_->RegisterInvocation(this);
 }
diff --git a/iree/rt/invocation.h b/iree/rt/invocation.h
index b2e79a5..600fd8a 100644
--- a/iree/rt/invocation.h
+++ b/iree/rt/invocation.h
@@ -27,6 +27,7 @@
 #include "iree/hal/buffer_view.h"
 #include "iree/rt/function.h"
 #include "iree/rt/policy.h"
+#include "iree/rt/stack.h"
 #include "iree/rt/stack_trace.h"
 
 namespace iree {
@@ -63,6 +64,10 @@
       absl::InlinedVector<hal::BufferView, 8> arguments,
       absl::optional<absl::InlinedVector<hal::BufferView, 8>> results =
           absl::nullopt);
+  static StatusOr<ref_ptr<Invocation>> Create(
+      ref_ptr<Context> context, const Function function, ref_ptr<Policy> policy,
+      absl::Span<const ref_ptr<Invocation>> dependencies,
+      absl::Span<const hal::BufferView> arguments);
 
   ~Invocation();
 
@@ -126,6 +131,8 @@
   const Function function_;
   ref_ptr<Policy> policy_;
 
+  Stack stack_;
+
   absl::Mutex status_mutex_;
   Status completion_status_ ABSL_GUARDED_BY(status_mutex_) =
       UnavailableErrorBuilder(IREE_LOC);
diff --git a/iree/rt/module.h b/iree/rt/module.h
index d81572e..1878d9d 100644
--- a/iree/rt/module.h
+++ b/iree/rt/module.h
@@ -27,7 +27,9 @@
 namespace iree {
 namespace rt {
 
+class Disassembler;
 class SourceResolver;
+class Stack;
 
 // Abstract compiled module interface for resolving functions.
 //
@@ -50,6 +52,11 @@
   // May be nullptr if debugging info has been stripped.
   virtual SourceResolver* source_resolver() const = 0;
 
+  // Returns a disassembler that can be used to disassemble functions in the
+  // module. May be nullptr if debugging info has been stripped or disassembly
+  // has been disabled as a compile option.
+  virtual Disassembler* disassembler() const = 0;
+
   // A short human-readable string that matches the compiler formatting.
   virtual std::string DebugStringShort() const = 0;
 
@@ -82,7 +89,7 @@
 
   // Temporary until scheduler is built.
   virtual Status Execute(
-      const Function function,
+      Stack* stack, const Function function,
       absl::InlinedVector<hal::BufferView, 8> arguments,
       absl::InlinedVector<hal::BufferView, 8>* results) const = 0;
 
diff --git a/iree/rt/module_printer.cc b/iree/rt/module_printer.cc
new file mode 100644
index 0000000..dd0267e
--- /dev/null
+++ b/iree/rt/module_printer.cc
@@ -0,0 +1,65 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "iree/rt/module_printer.h"
+
+#include <iomanip>
+
+#include "iree/rt/disassembler.h"
+#include "iree/rt/source_resolver.h"
+
+namespace iree {
+namespace rt {
+
+Status PrintModuleToStream(const Module& module, PrintModuleFlagBitfield flags,
+                           std::ostream* stream) {
+  *stream << "Imports:\n";
+  for (int i = 0; i < module.signature().import_function_count(); ++i) {
+    ASSIGN_OR_RETURN(auto function, module.LookupFunctionByOrdinal(
+                                        Function::Linkage::kImport, i));
+    *stream << "  " << i << ": " << function << "\n";
+  }
+  *stream << "Exports:\n";
+  for (int i = 0; i < module.signature().export_function_count(); ++i) {
+    ASSIGN_OR_RETURN(auto function, module.LookupFunctionByOrdinal(
+                                        Function::Linkage::kExport, i));
+    *stream << "  " << i << ": " << function << "\n";
+  }
+  if (module.signature().internal_function_count()) {
+    *stream << "Internal:\n";
+    auto* disassembler = module.disassembler();
+    for (int i = 0; i < module.signature().internal_function_count(); ++i) {
+      ASSIGN_OR_RETURN(auto function, module.LookupFunctionByOrdinal(
+                                          Function::Linkage::kInternal, i));
+      *stream << "  " << i << ": " << function << "\n";
+      if (disassembler && AllBitsSet(flags, PrintModuleFlag::kDisassemble)) {
+        auto instructions_or =
+            disassembler->DisassembleInstructions(function, 0);
+        if (IsUnavailable(instructions_or.status())) continue;
+        for (const auto& instruction : instructions_or.ValueOrDie()) {
+          *stream << "    " << std::setw(6) << instruction.offset << ": "
+                  << instruction.long_text << "\n";
+        }
+      }
+    }
+  }
+  return OkStatus();
+}
+
+Status PrintModuleToStream(const Module& module, std::ostream* stream) {
+  return PrintModuleToStream(module, PrintModuleFlag::kNone, stream);
+}
+
+}  // namespace rt
+}  // namespace iree
diff --git a/iree/vm/module_printer.h b/iree/rt/module_printer.h
similarity index 67%
rename from iree/vm/module_printer.h
rename to iree/rt/module_printer.h
index ee35fb3..945dbbd 100644
--- a/iree/vm/module_printer.h
+++ b/iree/rt/module_printer.h
@@ -12,33 +12,31 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef IREE_VM_MODULE_PRINTER_H_
-#define IREE_VM_MODULE_PRINTER_H_
+#ifndef IREE_RT_MODULE_PRINTER_H_
+#define IREE_RT_MODULE_PRINTER_H_
 
 #include <ostream>
 
 #include "iree/base/bitfield.h"
 #include "iree/base/status.h"
-#include "iree/vm/module.h"
-#include "iree/vm/opcode_info.h"
+#include "iree/rt/module.h"
 
 namespace iree {
-namespace vm {
+namespace rt {
 
 enum class PrintModuleFlag {
   kNone = 0,
-  kIncludeSourceMapping = 1,
+  kDisassemble = 1 << 0,
 };
 IREE_BITFIELD(PrintModuleFlag);
 using PrintModuleFlagBitfield = PrintModuleFlag;
 
 // Prints all functions within the module to the given |stream|.
-Status PrintModuleToStream(OpcodeTable opcode_table, const Module& module,
+Status PrintModuleToStream(const Module& module, std::ostream* stream);
+Status PrintModuleToStream(const Module& module, PrintModuleFlagBitfield flags,
                            std::ostream* stream);
-Status PrintModuleToStream(OpcodeTable opcode_table, const Module& module,
-                           PrintModuleFlagBitfield flags, std::ostream* stream);
 
-}  // namespace vm
+}  // namespace rt
 }  // namespace iree
 
-#endif  // IREE_VM_MODULE_PRINTER_H_
+#endif  // IREE_RT_MODULE_PRINTER_H_
diff --git a/iree/rt/source_location.cc b/iree/rt/source_location.cc
index c61645f..835905b 100644
--- a/iree/rt/source_location.cc
+++ b/iree/rt/source_location.cc
@@ -14,14 +14,18 @@
 
 #include "iree/rt/source_location.h"
 
+#include <sstream>
+
 #include "iree/rt/source_resolver.h"
 
 namespace iree {
 namespace rt {
 
 std::string SourceLocation::DebugStringShort() const {
-  // TODO(benvanik): ask source resolver.
-  return "<source>";
+  if (is_unknown()) return "(unknown)";
+  std::ostringstream stream;
+  resolver_->PrintSourceLocation(resolver_args_, &stream);
+  return stream.str();
 }
 
 }  // namespace rt
diff --git a/iree/rt/source_location.h b/iree/rt/source_location.h
index 9939e15..7db98ec 100644
--- a/iree/rt/source_location.h
+++ b/iree/rt/source_location.h
@@ -15,6 +15,7 @@
 #ifndef IREE_RT_SOURCE_LOCATION_H_
 #define IREE_RT_SOURCE_LOCATION_H_
 
+#include <array>
 #include <cstdint>
 #include <cstring>
 #include <ostream>
@@ -31,6 +32,10 @@
 // hash table.
 using SourceOffset = int64_t;
 
+// Implementation-defined opaque args stored within source locations that can be
+// used by a SourceResolver to map back to its internal storage.
+using SourceResolverArgs = std::array<uint64_t, 2>;
+
 // A location within a source file.
 // Only valid for the lifetime of the SourceResolver that returned it.
 class SourceLocation final {
@@ -40,15 +45,12 @@
 
   // Returns true if the two source locations reference the same target.
   inline static bool Equal(const SourceLocation& a, const SourceLocation& b) {
-    return a.resolver_ == b.resolver_ &&
-           std::memcmp(a.resolver_args_, b.resolver_args_,
-                       sizeof(a.resolver_args_)) == 0;
+    return a.resolver_ == b.resolver_ && a.resolver_args_ == b.resolver_args_;
   }
 
   SourceLocation() = default;
-  SourceLocation(SourceResolver* resolver, uintptr_t resolver_args[2])
-      : resolver_(resolver),
-        resolver_args_{resolver_args[0], resolver_args[1]} {}
+  SourceLocation(SourceResolver* resolver, SourceResolverArgs resolver_args)
+      : resolver_(resolver), resolver_args_(resolver_args) {}
 
   // A short one-line human readable string (such as file/line number).
   std::string DebugStringShort() const;
@@ -63,7 +65,7 @@
 
  private:
   SourceResolver* resolver_ = nullptr;
-  uintptr_t resolver_args_[2] = {0, 0};
+  SourceResolverArgs resolver_args_ = {0, 0};
 };
 
 inline bool operator==(const SourceLocation& a, const SourceLocation& b) {
diff --git a/iree/rt/source_resolver.h b/iree/rt/source_resolver.h
index fd677a2..3ceb1b9 100644
--- a/iree/rt/source_resolver.h
+++ b/iree/rt/source_resolver.h
@@ -15,14 +15,21 @@
 #ifndef IREE_RT_SOURCE_RESOLVER_H_
 #define IREE_RT_SOURCE_RESOLVER_H_
 
+#include <cstdint>
+#include <ostream>
+#include <vector>
+
+#include "absl/strings/string_view.h"
 #include "absl/types/optional.h"
+#include "iree/base/status.h"
 #include "iree/rt/function.h"
 #include "iree/rt/source_location.h"
 
 namespace iree {
 namespace rt {
 
-// Resolves offsets within functions to SourceLocations.
+// Resolves offsets within functions to SourceLocations and provides source
+// language services.
 //
 // Thread-safe.
 class SourceResolver {
@@ -34,6 +41,11 @@
   virtual absl::optional<SourceLocation> ResolveFunctionOffset(
       const Function& function, SourceOffset offset) = 0;
 
+  // Converts a source location to a human-readable string, commonly in a single
+  // line denoting an original source file location (such as path:line:col).
+  virtual void PrintSourceLocation(SourceResolverArgs resolver_args,
+                                   std::ostream* stream) const = 0;
+
   // TODO(benvanik): query local variable names.
 
   // TODO(benvanik): step target calculation (relative mapping).
diff --git a/iree/rt/stack.cc b/iree/rt/stack.cc
new file mode 100644
index 0000000..f4f300f
--- /dev/null
+++ b/iree/rt/stack.cc
@@ -0,0 +1,60 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "iree/rt/stack.h"
+
+#include <iterator>
+
+#include "absl/strings/str_join.h"
+#include "iree/base/status.h"
+
+namespace iree {
+namespace rt {
+
+constexpr int Stack::kMaxStackDepth;
+
+Stack::Stack(Context* context) : context_(context) {}
+
+Stack::~Stack() = default;
+
+StatusOr<StackFrame*> Stack::PushFrame(Function function) {
+  if (stack_depth_ + 1 > kMaxStackDepth) {
+    return InternalErrorBuilder(IREE_LOC)
+           << "Max stack depth of " << kMaxStackDepth << " exceeded";
+  }
+  frames_[stack_depth_++] = StackFrame(function);
+
+  // TODO(benvanik): WTF scope enter.
+
+  return current_frame();
+}
+
+Status Stack::PopFrame() {
+  if (stack_depth_ == 0) {
+    return InternalErrorBuilder(IREE_LOC) << "Unbalanced stack pop";
+  }
+
+  // TODO(benvanik): WTF scope leave.
+
+  --stack_depth_;
+  frames_[stack_depth_] = {};
+  return OkStatus();
+}
+
+std::string Stack::DebugString() const {
+  return absl::StrJoin(frames(), "\n", StackFrameFormatter());
+}
+
+}  // namespace rt
+}  // namespace iree
diff --git a/iree/rt/stack.h b/iree/rt/stack.h
new file mode 100644
index 0000000..a9547fe
--- /dev/null
+++ b/iree/rt/stack.h
@@ -0,0 +1,85 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_RT_STACK_H_
+#define IREE_RT_STACK_H_
+
+#include <functional>
+
+#include "absl/types/span.h"
+#include "iree/base/status.h"
+#include "iree/rt/stack_frame.h"
+
+namespace iree {
+namespace rt {
+
+class Context;
+
+// A runtime call stack for managing stack frames.
+// The frames within a stack may be from different backends and may provide
+// varying levels of information based on capabilities.
+//
+// Thread-compatible. Do not attempt to investigate a stack while another thread
+// may be mutating it!
+class Stack final {
+ public:
+  static constexpr int kMaxStackDepth = 32;
+
+  explicit Stack(Context* context);
+  Stack(const Stack&) = delete;
+  Stack& operator=(const Stack&) = delete;
+  ~Stack();
+
+  // Context defining the module and global workspaces.
+  Context* context() const { return context_; }
+
+  // All stack frames within the stack.
+  absl::Span<StackFrame> frames() {
+    return absl::MakeSpan(frames_).subspan(0, stack_depth_);
+  }
+  absl::Span<const StackFrame> frames() const {
+    return absl::MakeConstSpan(frames_).subspan(0, stack_depth_);
+  }
+
+  // The current stack frame.
+  StackFrame* current_frame() {
+    return stack_depth_ > 0 ? &frames_[stack_depth_ - 1] : nullptr;
+  }
+
+  // The stack frame of the caller of the current function.
+  StackFrame* caller_frame() {
+    return stack_depth_ > 1 ? &frames_[stack_depth_ - 2] : nullptr;
+  }
+
+  StatusOr<StackFrame*> PushFrame(Function function);
+  Status PopFrame();
+
+  // Returns a full stack frame listing in human-readable form.
+  std::string DebugString() const;
+
+ private:
+  Context* context_ = nullptr;
+  std::array<StackFrame, kMaxStackDepth> frames_;
+  int stack_depth_ = 0;
+};
+
+inline std::ostream& operator<<(std::ostream& stream, const Stack& stack) {
+  stream << stack.DebugString();
+  return stream;
+}
+
+}  // namespace rt
+}  // namespace iree
+
+#endif  // IREE_RT_STACK_H_
diff --git a/iree/rt/stack_frame.h b/iree/rt/stack_frame.h
index 573e48e..bb77ef4 100644
--- a/iree/rt/stack_frame.h
+++ b/iree/rt/stack_frame.h
@@ -17,6 +17,7 @@
 
 #include <ostream>
 
+#include "absl/types/span.h"
 #include "iree/rt/function.h"
 #include "iree/rt/module.h"
 #include "iree/rt/source_location.h"
@@ -24,6 +25,12 @@
 namespace iree {
 namespace rt {
 
+// TODO(benvanik): allocate in-place from an arena.
+// Register table used within a stack frame.
+struct Registers {
+  std::vector<hal::BufferView> buffer_views;
+};
+
 // A single frame on the call stack containing current execution state and
 // register values.
 //
@@ -35,12 +42,15 @@
 // but instead just routing to the real storage via indirection. If the debugger
 // is not attached and no errors are hit then no additional bookkeeping is done.
 //
-// Thread-compatible, as is the owning StackTrace.
+// Thread-compatible, as is the owning Stack/StackTrace.
 class StackFrame final {
  public:
   StackFrame() = default;
-  explicit StackFrame(Function function, SourceOffset offset)
-      : function_(function), offset_(offset) {}
+  explicit StackFrame(Function function) : function_(function) {}
+  StackFrame(Function function, SourceOffset offset, Registers registers)
+      : function_(function),
+        offset_(offset),
+        registers_(std::move(registers)) {}
   StackFrame(const StackFrame&) = delete;
   StackFrame& operator=(const StackFrame&) = delete;
   StackFrame(StackFrame&&) = default;
@@ -57,15 +67,17 @@
   // treat them as opaque and must use the SourceResolver to compute new
   // offsets (such as 'next offset').
   SourceOffset offset() const { return offset_; }
+  SourceOffset* mutable_offset() { return &offset_; }
 
   // Returns a source location, if available, for the current offset within the
   // target function.
   absl::optional<SourceLocation> source_location() const;
 
-  // TODO(benvanik): register access:
-  //   query total register layout for stack frame
-  //   get register in stack frame
-  //   set register in stack frame
+  // Registers used within the stack frame.
+  // Storage is implementation-defined and is valid only for the lifetime of the
+  // frame.
+  const Registers& registers() const { return registers_; }
+  Registers* mutable_registers() { return &registers_; }
 
   // A short human-readable string for the frame; a single line.
   std::string DebugStringShort() const;
@@ -73,6 +85,13 @@
  private:
   Function function_;
   SourceOffset offset_ = 0;
+  Registers registers_;
+};
+
+struct StackFrameFormatter {
+  void operator()(std::string* out, const StackFrame& stack_frame) const {
+    out->append(stack_frame.DebugStringShort());
+  }
 };
 
 inline std::ostream& operator<<(std::ostream& stream,
diff --git a/iree/rt/stack_trace.cc b/iree/rt/stack_trace.cc
index a5fdf33..ace8803 100644
--- a/iree/rt/stack_trace.cc
+++ b/iree/rt/stack_trace.cc
@@ -20,14 +20,6 @@
 namespace iree {
 namespace rt {
 
-namespace {
-struct StackFrameFormatter {
-  void operator()(std::string* out, const StackFrame& stack_frame) const {
-    out->append(stack_frame.DebugStringShort());
-  }
-};
-}  // namespace
-
 std::string StackTrace::DebugString() const {
   return absl::StrJoin(frames_, "\n", StackFrameFormatter());
 }
diff --git a/iree/rt/stack_trace.h b/iree/rt/stack_trace.h
index 0c0612b..beca466 100644
--- a/iree/rt/stack_trace.h
+++ b/iree/rt/stack_trace.h
@@ -25,12 +25,13 @@
 namespace iree {
 namespace rt {
 
-// A snapshot of the runtime callstack providing access to stack frames.
+// A snapshot of a stack at a point in time.
 // The frames within a stack may be from different backends and may provide
 // varying levels of information based on capabilities.
 //
-// Thread-compatible. Execution on one thread and stack manipulation on another
-// must be externally synchronized by the caller.
+// Depending on the capture options the trace may contain references to register
+// values (such as buffers) from the time of capture. If the buffers were
+// modified after the capture was taken those results will be reflected!
 class StackTrace final {
  public:
   StackTrace() = default;
@@ -45,16 +46,6 @@
     return absl::MakeConstSpan(frames_);
   }
 
-  // The current stack frame.
-  const StackFrame* current_frame() const {
-    return !frames_.empty() ? &frames_[frames_.size() - 1] : nullptr;
-  }
-
-  // The stack frame of the caller of the current function.
-  const StackFrame* caller_frame() const {
-    return frames_.size() > 1 ? &frames_[frames_.size() - 2] : nullptr;
-  }
-
   // Returns a full stack frame listing in human-readable form.
   std::string DebugString() const;
 
diff --git a/iree/samples/CMakeLists.txt b/iree/samples/CMakeLists.txt
index 48bf62c..ae75ec9 100644
--- a/iree/samples/CMakeLists.txt
+++ b/iree/samples/CMakeLists.txt
@@ -13,4 +13,4 @@
 # limitations under the License.
 
 add_subdirectory(hal)
-add_subdirectory(vm)
+add_subdirectory(rt)
diff --git a/iree/samples/hal/BUILD b/iree/samples/hal/BUILD
index f29844b..8fa22bf 100644
--- a/iree/samples/hal/BUILD
+++ b/iree/samples/hal/BUILD
@@ -2,14 +2,14 @@
 # These do not rely on higher layers of the system (such as the VM or runtime).
 
 load("//iree:build_defs.bzl", "PLATFORM_VULKAN_TEST_DEPS")
-load("//iree/tools:compilation.bzl", "iree_module")
+load("//iree/tools:compilation.bzl", "iree_bytecode_module")
 
 package(
     default_visibility = ["//visibility:public"],
     licenses = ["notice"],  # Apache 2.0
 )
 
-iree_module(
+iree_bytecode_module(
     name = "simple_compute_test_module",
     srcs = ["simple_compute_test.mlir"],
     cc_namespace = "iree::hal::samples",
diff --git a/iree/samples/hal/simple_compute_test.cc b/iree/samples/hal/simple_compute_test.cc
index ac2986b..43f0c48 100644
--- a/iree/samples/hal/simple_compute_test.cc
+++ b/iree/samples/hal/simple_compute_test.cc
@@ -21,8 +21,8 @@
 // imports requiring runtime support, uses floats exclusively (as that's assumed
 // available everywhere), etc.
 //
-// The `iree_module` build rule is used to translate the MLIR to the module
-// flatbuffer. Additional target support can be defined there.
+// The `iree_bytecode_module` build rule is used to translate the MLIR to the
+// module flatbuffer. Additional target support can be defined there.
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/iree/samples/vm/BUILD b/iree/samples/rt/BUILD
similarity index 69%
rename from iree/samples/vm/BUILD
rename to iree/samples/rt/BUILD
index 2a1a94c..2712b07 100644
--- a/iree/samples/vm/BUILD
+++ b/iree/samples/rt/BUILD
@@ -1,21 +1,21 @@
-# Samples demonstrating use of the VM API.
+# Samples demonstrating use of the RT API.
 
-load("//iree/tools:compilation.bzl", "iree_module")
+load("//iree/tools:compilation.bzl", "iree_bytecode_module")
 
 package(
     default_visibility = ["//visibility:public"],
     licenses = ["notice"],  # Apache 2.0
 )
 
-iree_module(
-    name = "simple_module_test_module",
+iree_bytecode_module(
+    name = "simple_module_test_bytecode_module",
     srcs = ["simple_module_test.mlir"],
-    cc_namespace = "iree::vm::samples",
+    cc_namespace = "iree::rt::samples",
 )
 
 cc_test(
-    name = "simple_module_test",
-    srcs = ["simple_module_test.cc"],
+    name = "bytecode_module_test",
+    srcs = ["bytecode_module_test.cc"],
     data = [
         # When building with --config=asan you must specify the following
         # envvar when using Vulkan + a local Nvidia GPU:
@@ -23,20 +23,18 @@
         "//iree/tools:sanitizer_suppressions.txt",
     ],
     deps = [
-        ":simple_module_test_module_cc",
+        ":simple_module_test_bytecode_module_cc",
         "@com_google_googletest//:gtest_main",
         "@com_google_absl//absl/strings",
         "//iree/base:flatbuffer_util",
+        "//iree/base:status",
         "//iree/base:status_matchers",
         "//iree/hal:buffer_view",
-        "//iree/hal:command_buffer",
-        "//iree/hal:command_queue",
         "//iree/hal:driver_registry",
+        "//iree/rt",
         "//iree/schemas",
-        "//iree/base:status",
-        "//iree/vm:fiber_state",
-        "//iree/vm:instance",
-        "//iree/vm:sequencer_context",
+        "//iree/vm:bytecode_module",
+        "//iree/vm:sequencer_module",
 
         # These are the drivers we support running with and can produce
         # executables for from the source MLIR.
diff --git a/iree/samples/vm/CMakeLists.txt b/iree/samples/rt/CMakeLists.txt
similarity index 82%
rename from iree/samples/vm/CMakeLists.txt
rename to iree/samples/rt/CMakeLists.txt
index 1c36407..ec6ee74 100644
--- a/iree/samples/vm/CMakeLists.txt
+++ b/iree/samples/rt/CMakeLists.txt
@@ -14,9 +14,9 @@
 
 iree_cc_test(
   NAME
-    simple_module_test
+    bytecode_module_test
   SRCS
-    "simple_module_test.cc"
+    "bytecode_module_test.cc"
   DEPS
     absl::strings
     gtest_main
@@ -24,13 +24,10 @@
     iree::base::status
     iree::base::status_matchers
     iree::hal::buffer_view
-    iree::hal::command_buffer
-    iree::hal::command_queue
     iree::hal::driver_registry
     iree::schemas
-    iree::vm::fiber_state
-    iree::vm::instance
-    iree::vm::sequencer_context
+    iree::rt
+    iree::vm::bytecode_module
     # Enabled drivers:
     iree::hal::interpreter::interpreter_driver_module
     iree::hal::vulkan::vulkan_driver_module
diff --git a/iree/samples/vm/simple_module_test.cc b/iree/samples/rt/bytecode_module_test.cc
similarity index 79%
rename from iree/samples/vm/simple_module_test.cc
rename to iree/samples/rt/bytecode_module_test.cc
index 8db859e..2fdc7cf 100644
--- a/iree/samples/vm/simple_module_test.cc
+++ b/iree/samples/rt/bytecode_module_test.cc
@@ -25,6 +25,8 @@
 // The `iree_module` build rule is used to translate the MLIR to the module
 // flatbuffer. Additional HAL backend target support can be defined there.
 
+#include "iree/vm/bytecode_module.h"
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/strings/str_replace.h"
@@ -32,23 +34,20 @@
 #include "iree/base/status.h"
 #include "iree/base/status_matchers.h"
 #include "iree/hal/buffer_view.h"
-#include "iree/hal/command_buffer.h"
-#include "iree/hal/command_queue.h"
 #include "iree/hal/driver_registry.h"
-#include "iree/samples/vm/simple_module_test_module.h"
+#include "iree/rt/context.h"
+#include "iree/rt/instance.h"
+#include "iree/samples/rt/simple_module_test_bytecode_module.h"
 #include "iree/schemas/module_def_generated.h"
-#include "iree/vm/fiber_state.h"
-#include "iree/vm/instance.h"
-#include "iree/vm/sequencer_context.h"
+#include "iree/vm/sequencer_module.h"
 
 namespace iree {
-namespace vm {
+namespace rt {
 namespace samples {
 namespace {
 
-using iree::hal::BufferView;
-
-using ModuleFile = FlatBufferFile<ModuleDef>;
+using ::iree::hal::BufferView;
+using ::iree::vm::ModuleFile;
 
 struct TestParams {
   // HAL driver to use for the test.
@@ -77,7 +76,7 @@
 };
 
 TEST_P(SimpleModuleTest, RunOnce) {
-  auto instance = std::make_shared<Instance>();
+  auto instance = make_ref<Instance>();
 
   // Create driver for this test (based on params) and then get a default
   // device.
@@ -105,22 +104,20 @@
   // Make a new context and load the precompiled module file (from
   // simple_module_test.mlir) into it.
   LOG(INFO) << "Loading simple_module_test.mlir...";
-  SequencerContext context(instance);
-  const auto* module_file_toc = simple_module_test_module_create();
-  ASSERT_OK_AND_ASSIGN(
-      auto module_file,
-      ModuleFile::WrapBuffer(ModuleDefIdentifier(),
-                             absl::MakeSpan(reinterpret_cast<const uint8_t*>(
-                                                module_file_toc->data),
-                                            module_file_toc->size)));
+  auto policy = make_ref<Policy>();
+  Context context(add_ref(instance), add_ref(policy));
+  const auto* module_file_toc = simple_module_test_bytecode_module_create();
+  ASSERT_OK_AND_ASSIGN(auto module_file,
+                       vm::ModuleFile::WrapBuffer(
+                           ModuleDefIdentifier(),
+                           absl::MakeSpan(reinterpret_cast<const uint8_t*>(
+                                              module_file_toc->data),
+                                          module_file_toc->size)));
   ASSERT_OK_AND_ASSIGN(auto main_module,
-                       Module::FromFile(std::move(module_file)));
+                       vm::SequencerModule::FromFile(std::move(module_file)));
   ASSERT_OK(context.RegisterModule(std::move(main_module)));
   LOG(INFO) << "Module loaded and context is ready for use";
 
-  // In this sample we just have a single fiber.
-  FiberState fiber_state(instance);
-
   // Allocate buffers that can be mapped on the CPU and that can also be used
   // on the device. Not all devices support this, but the ones we have now do.
   LOG(INFO) << "Creating I/O buffers...";
@@ -140,14 +137,17 @@
 
   // Call into the @simple_mul function.
   LOG(INFO) << "Calling @simple_mul...";
-  std::vector<BufferView> args{
+  absl::InlinedVector<BufferView, 8> args{
       BufferView{add_ref(arg0_buffer), {kElementCount}, sizeof(float)},
       BufferView{add_ref(arg1_buffer), {kElementCount}, sizeof(float)},
   };
-  std::vector<BufferView> results{1};
-  ASSERT_OK_AND_ASSIGN(auto simple_mul, context.LookupExport("simple_mul"));
-  ASSERT_OK(context.Invoke(&fiber_state, simple_mul, absl::MakeSpan(args),
-                           absl::MakeSpan(results)));
+  ASSERT_OK_AND_ASSIGN(auto simple_mul,
+                       context.ResolveFunction("module.simple_mul"));
+  ASSERT_OK_AND_ASSIGN(auto invocation,
+                       Invocation::Create(add_ref(&context), simple_mul,
+                                          nullptr, {}, std::move(args)));
+  ASSERT_OK(invocation->Await(absl::InfiniteFuture()));
+  ASSERT_OK_AND_ASSIGN(auto results, invocation->ConsumeResults());
 
   // Read back the results and ensure we got the right values.
   LOG(INFO) << "Reading back results...";
@@ -166,5 +166,5 @@
 
 }  // namespace
 }  // namespace samples
-}  // namespace vm
+}  // namespace rt
 }  // namespace iree
diff --git a/iree/samples/vm/simple_module_test.mlir b/iree/samples/rt/simple_module_test.mlir
similarity index 100%
rename from iree/samples/vm/simple_module_test.mlir
rename to iree/samples/rt/simple_module_test.mlir
diff --git a/iree/tools/BUILD b/iree/tools/BUILD
index 6d15ade..3fa849c 100644
--- a/iree/tools/BUILD
+++ b/iree/tools/BUILD
@@ -37,6 +37,8 @@
         "@com_google_absl//absl/flags:flag",
         "@com_google_absl//absl/strings",
         "//iree/base:source_location",
+        "//iree/rt",
+        "//iree/vm:sequencer_module",
         "@llvm//:support",
         "@local_config_mlir//:IR",
         "@local_config_mlir//:Parser",
@@ -48,19 +50,14 @@
         "//iree/compiler/Translation/SPIRV",
         "//iree/hal:buffer_view_string_util",
         "//iree/hal:driver_registry",
+        "//iree/schemas",
+        "//iree/rt/debug:debug_server_flags",
+    ] + PLATFORM_VULKAN_DEPS + [
         "//iree/hal/interpreter:interpreter_driver_module",
-        "//iree/hal/vulkan:vulkan_driver_module",
         # TODO(b/142004903): enable when Dawn HAL implementation is functional
         # "//iree/hal/dawn:dawn_driver_module",
-        "//iree/schemas",
-        "//iree/vm:bytecode_tables_sequencer",
-        "//iree/vm:fiber_state",
-        "//iree/vm:instance",
-        "//iree/vm:module",
-        "//iree/vm:module_printer",
-        "//iree/vm:sequencer_context",
-        "//iree/rt/debug:debug_server_flags",
-    ] + PLATFORM_VULKAN_DEPS,
+        "//iree/hal/vulkan:vulkan_driver_module",
+    ],
 )
 
 cc_binary(
@@ -94,16 +91,10 @@
         "//iree/hal:buffer_view_string_util",
         "//iree/hal:driver_registry",
         "//iree/hal/interpreter:interpreter_driver_module",
+        "//iree/rt",
         "//iree/rt/debug:debug_server_flags",
         "//iree/schemas",
-        "//iree/vm:bytecode_printer",
-        "//iree/vm:bytecode_tables_sequencer",
-        "//iree/vm:fiber_state",
-        "//iree/vm:function",
-        "//iree/vm:instance",
-        "//iree/vm:module",
-        "//iree/vm:module_printer",
-        "//iree/vm:sequencer_context",
+        "//iree/vm:sequencer_module",
         "@com_google_absl//absl/flags:flag",
         "@com_google_absl//absl/strings",
     ],
diff --git a/iree/tools/compilation.bzl b/iree/tools/compilation.bzl
index 5d1ffe5..b232099 100644
--- a/iree/tools/compilation.bzl
+++ b/iree/tools/compilation.bzl
@@ -3,7 +3,7 @@
 load("//build_tools/embed_data:build_defs.bzl", "cc_embed_data")
 
 # TODO(benvanik): port to a full starlark rule, document, etc.
-def iree_module(
+def iree_bytecode_module(
         name,
         srcs,
         cc_namespace = None,
diff --git a/iree/tools/debugger/BUILD b/iree/tools/debugger/BUILD
index b1f432b..e3b90a4 100644
--- a/iree/tools/debugger/BUILD
+++ b/iree/tools/debugger/BUILD
@@ -7,7 +7,8 @@
 # By default the IREE runtime does not compile in debug support. To link it in
 # pass --define=IREE_DEBUG=1 to bazel builds of the runtime.
 
-load("//third_party/emscripten:split_transition_defs.bzl", "auto_wasm_binary")
+# TODO(benvanik): re-enable debugger after refactoring.
+# load("//third_party/emscripten:split_transition_defs.bzl", "auto_wasm_binary")
 
 package(
     default_visibility = ["//visibility:public"],
@@ -41,10 +42,6 @@
 #         "//iree/base:status",
 #         "//iree/rt/debug:debug_client",
 #         "//iree/schemas",
-#         "//iree/vm:bytecode_printer",
-#         "//iree/vm:bytecode_tables_sequencer",
-#         "//iree/vm:module",
-#         "//iree/vm:source_map",
 #     ],
 # )
 #
diff --git a/iree/tools/debugger/debug_app.cc b/iree/tools/debugger/debug_app.cc
index 793f4a1..303979e 100644
--- a/iree/tools/debugger/debug_app.cc
+++ b/iree/tools/debugger/debug_app.cc
@@ -31,10 +31,8 @@
 #include "iree/base/status.h"
 #include "iree/rt/debug/debug_client.h"
 #include "iree/schemas/debug_service_generated.h"
-#include "iree/vm/bytecode_printer.h"
+#include "iree/vm/bytecode_module.h"
 #include "iree/vm/bytecode_tables_sequencer.h"
-#include "iree/vm/module.h"
-#include "iree/vm/source_map.h"
 
 namespace iree {
 namespace rt {
@@ -1311,21 +1309,7 @@
   auto* remote_module = document->function->module();
   auto* remote_function = document->function;
 
-  ASSIGN_OR_RETURN(auto module, vm::Module::FromDef(remote_module->def()));
-
-  // TODO(benvanik): source map support.
-  // Want line count including source lines, IR lines, and bytecode lines.
-  // May want lower level (JIT/etc) lines too.
-
-  // TODO(benvanik): bytecode iterator for richer display.
-  auto source_map_resolver = vm::SourceMapResolver::FromFunction(
-      module->def(), remote_function->ordinal());
-  vm::BytecodePrinter printer(vm::sequencer_opcode_table(),
-                              module->function_table(),
-                              module->executable_table(), source_map_resolver);
-  ASSIGN_OR_RETURN(std::string full_string,
-                   printer.Print(*remote_function->bytecode()));
-  document->bytecode_info.lines = absl::StrSplit(full_string, '\n');
+  document->bytecode_info.lines = remote_function->name();
 
   return OkStatus();
 }
diff --git a/iree/tools/run_mlir_main.cc b/iree/tools/run_mlir_main.cc
index 360745e..dfa3486 100644
--- a/iree/tools/run_mlir_main.cc
+++ b/iree/tools/run_mlir_main.cc
@@ -47,14 +47,14 @@
 #include "iree/compiler/Translation/Sequencer/SequencerModuleTranslation.h"
 #include "iree/hal/buffer_view_string_util.h"
 #include "iree/hal/driver_registry.h"
+#include "iree/rt/context.h"
 #include "iree/rt/debug/debug_server_flags.h"
+#include "iree/rt/instance.h"
+#include "iree/rt/invocation.h"
+#include "iree/rt/module.h"
+#include "iree/rt/module_printer.h"
 #include "iree/schemas/module_def_generated.h"
-#include "iree/vm/bytecode_tables_sequencer.h"
-#include "iree/vm/fiber_state.h"
-#include "iree/vm/instance.h"
-#include "iree/vm/module.h"
-#include "iree/vm/module_printer.h"
-#include "iree/vm/sequencer_context.h"
+#include "iree/vm/sequencer_module.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/SourceMgr.h"
 #include "mlir/IR/Attributes.h"
@@ -92,9 +92,8 @@
 namespace {
 
 using ::iree::hal::BufferView;
-using ::iree::vm::Function;
-using ::iree::vm::Module;
-using ::iree::vm::ModuleFile;
+using ::iree::rt::Function;
+using ::iree::rt::Module;
 
 // Returns a driver name capable of handling input from the given backend.
 std::string BackendToDriverName(std::string backend) {
@@ -107,7 +106,7 @@
 }
 
 // Prepares a module for evaluation by running MLIR import and IREE translation.
-StatusOr<std::unique_ptr<Module>> PrepareModule(
+StatusOr<ref_ptr<Module>> PrepareModule(
     std::string target_backend,
     std::unique_ptr<llvm::MemoryBuffer> file_buffer) {
   mlir::MLIRContext context;
@@ -142,9 +141,9 @@
 
   // Wrap module in a file handle.
   ASSIGN_OR_RETURN(auto iree_module_file,
-                   ModuleFile::FromBuffer(ModuleDefIdentifier(),
-                                          std::move(iree_module_bytes)));
-  return Module::FromFile(std::move(iree_module_file));
+                   vm::ModuleFile::FromBuffer(ModuleDefIdentifier(),
+                                              std::move(iree_module_bytes)));
+  return vm::SequencerModule::FromFile(std::move(iree_module_file));
 }
 
 // Parses a list of input shapes and values from a string of newline-separated
@@ -196,25 +195,22 @@
 }
 
 // Evaluates a single function in its own fiber, printing the results to stdout.
-Status EvaluateFunction(std::shared_ptr<vm::Instance> instance,
-                        vm::SequencerContext* context,
+Status EvaluateFunction(const ref_ptr<rt::Context>& context,
                         hal::Allocator* allocator, const Function& function) {
-  // Setup our dummy fiber we will run with.
-  vm::FiberState fiber_state(instance);
-
   std::cout << "EXEC @" << function.name() << std::endl;
 
-  // Marshal inputs.
-  ASSIGN_OR_RETURN(std::vector<BufferView> args,
-                   ParseInputsFromFlags(allocator));
-  std::vector<BufferView> results;
-  results.resize(function.result_count());
+  // Create invocation that will perform the execution.
+  ASSIGN_OR_RETURN(auto arguments, ParseInputsFromFlags(allocator));
+  ASSIGN_OR_RETURN(
+      auto invocation,
+      rt::Invocation::Create(add_ref(context), function, make_ref<rt::Policy>(),
+                             {}, absl::MakeConstSpan(arguments)));
 
-  // Call into the main function.
-  RETURN_IF_ERROR(context->Invoke(&fiber_state, function, absl::MakeSpan(args),
-                                  absl::MakeSpan(results)));
+  // Wait until invocation completes.
+  RETURN_IF_ERROR(invocation->Await(absl::InfiniteFuture()));
 
   // Print outputs.
+  ASSIGN_OR_RETURN(auto results, invocation->ConsumeResults());
   RETURN_IF_ERROR(OutputFunctionResults(function, absl::MakeSpan(results)));
 
   return OkStatus();
@@ -222,35 +218,33 @@
 
 // Evaluates all exported functions within given module.
 Status EvaluateFunctions(absl::string_view target_backend,
-                         std::unique_ptr<Module> module) {
+                         ref_ptr<Module> module) {
   // Create the context we'll use for this (ensuring that we can't interfere
   // with other running evaluations, such as when in a multithreaded test
   // runner).
   ASSIGN_OR_RETURN(auto debug_server, rt::debug::CreateDebugServerFromFlags());
-  auto instance = std::make_shared<vm::Instance>();
+  auto instance = make_ref<rt::Instance>(std::move(debug_server));
   ASSIGN_OR_RETURN(auto driver, hal::DriverRegistry::shared_registry()->Create(
                                     target_backend));
   ASSIGN_OR_RETURN(auto device, driver->CreateDefaultDevice());
   RETURN_IF_ERROR(instance->device_manager()->RegisterDevice(device));
-  vm::SequencerContext context(instance);
 
   if (absl::GetFlag(FLAGS_print_bytecode)) {
-    vm::PrintModuleFlagBitfield print_flags = vm::PrintModuleFlag::kNone;
-    RETURN_IF_ERROR(vm::PrintModuleToStream(vm::sequencer_opcode_table(),
-                                            *module, print_flags, &std::cout));
+    RETURN_IF_ERROR(rt::PrintModuleToStream(
+        *module, rt::PrintModuleFlag::kDisassemble, &std::cout));
   }
 
-  // Register module with the context.
-  RETURN_IF_ERROR(context.RegisterModule(std::move(module)));
-
   // Evaluate all exported functions.
-  for (auto& module : context.modules()) {
-    for (int function_ordinal : *module->def().function_table()->exports()) {
-      ASSIGN_OR_RETURN(auto function, module->function_table().LookupFunction(
-                                          function_ordinal));
-      RETURN_IF_ERROR(
-          EvaluateFunction(instance, &context, device->allocator(), function));
-    }
+  auto policy = make_ref<rt::Policy>();
+  for (int i = 0; i < module->signature().export_function_count(); ++i) {
+    // Setup a new context for this invocation.
+    auto context = make_ref<rt::Context>(add_ref(instance), add_ref(policy));
+    RETURN_IF_ERROR(context->RegisterModule(add_ref(module)));
+
+    // Invoke the function and print results.
+    ASSIGN_OR_RETURN(auto function, module->LookupFunctionByOrdinal(
+                                        rt::Function::Linkage::kExport, i));
+    RETURN_IF_ERROR(EvaluateFunction(context, device->allocator(), function));
   }
 
   RETURN_IF_ERROR(instance->device_manager()->UnregisterDevice(device.get()));
diff --git a/iree/tools/run_module_main.cc b/iree/tools/run_module_main.cc
index 5fefbfc..be634ee 100644
--- a/iree/tools/run_module_main.cc
+++ b/iree/tools/run_module_main.cc
@@ -27,23 +27,19 @@
 #include "iree/base/status.h"
 #include "iree/hal/buffer_view_string_util.h"
 #include "iree/hal/driver_registry.h"
+#include "iree/rt/context.h"
 #include "iree/rt/debug/debug_server_flags.h"
+#include "iree/rt/instance.h"
+#include "iree/rt/module_printer.h"
 #include "iree/schemas/module_def_generated.h"
-#include "iree/vm/bytecode_printer.h"
-#include "iree/vm/bytecode_tables_sequencer.h"
-#include "iree/vm/fiber_state.h"
-#include "iree/vm/function.h"
-#include "iree/vm/instance.h"
-#include "iree/vm/module.h"
-#include "iree/vm/module_printer.h"
-#include "iree/vm/sequencer_context.h"
+#include "iree/vm/sequencer_module.h"
 
 ABSL_FLAG(std::string, main_module, "", "Main module with entry point.");
 ABSL_FLAG(std::string, main_function, "",
           "Function within the main module to execute.");
 
-ABSL_FLAG(bool, print_source_info, false,
-          "Prints source map information in bytecode output.");
+ABSL_FLAG(bool, print_disassembly, true,
+          "Prints bytecode disassembly for the module.");
 
 ABSL_FLAG(std::string, input_values, "", "Input shapes and optional values.");
 ABSL_FLAG(std::string, input_file, "",
@@ -54,18 +50,15 @@
           "binary/signed int/unsigned int/float).");
 
 namespace iree {
-namespace vm {
 namespace {
 
-using ::iree::hal::BufferView;
-
 // Parses a list of input shapes and values from a string of newline-separated
 // inputs. Expects the contents to have one value per line with each value
 // listed as
 //   [shape]xtype=[value]
 // Example:
 //   4x4xi8=0,1,2,3
-StatusOr<std::vector<BufferView>> ParseInputsFromFlags(
+StatusOr<std::vector<hal::BufferView>> ParseInputsFromFlags(
     hal::Allocator* allocator) {
   std::string file_contents;
   if (!absl::GetFlag(FLAGS_input_values).empty()) {
@@ -75,7 +68,7 @@
     ASSIGN_OR_RETURN(file_contents,
                      file_io::GetFileContents(absl::GetFlag(FLAGS_input_file)));
   }
-  std::vector<BufferView> inputs;
+  std::vector<hal::BufferView> inputs;
   for (const auto& line :
        absl::StrSplit(file_contents, '\n', absl::SkipWhitespace())) {
     ASSIGN_OR_RETURN(auto input,
@@ -89,81 +82,67 @@
 
 Status Run() {
   ASSIGN_OR_RETURN(auto debug_server, rt::debug::CreateDebugServerFromFlags());
-  auto instance = std::make_shared<Instance>();
+  auto instance = make_ref<rt::Instance>(std::move(debug_server));
   ASSIGN_OR_RETURN(auto driver, hal::DriverRegistry::shared_registry()->Create(
                                     "interpreter"));
   ASSIGN_OR_RETURN(auto device, driver->CreateDefaultDevice());
   RETURN_IF_ERROR(instance->device_manager()->RegisterDevice(device));
-  SequencerContext context(instance);
+  auto policy = make_ref<rt::Policy>();
+  auto context = make_ref<rt::Context>(add_ref(instance), std::move(policy));
 
   // Load main module.
   ASSIGN_OR_RETURN(
       auto main_module_file,
-      ModuleFile::LoadFile(ModuleDefIdentifier(),
-                           absl::GetFlag(FLAGS_main_module)),
+      vm::ModuleFile::LoadFile(ModuleDefIdentifier(),
+                               absl::GetFlag(FLAGS_main_module)),
       _ << "while loading module file " << absl::GetFlag(FLAGS_main_module));
   ASSIGN_OR_RETURN(auto main_module,
-                   Module::FromFile(std::move(main_module_file)));
-
-  // Add native functions for use by the module.
-  RETURN_IF_ERROR(context.RegisterNativeFunction(
-      "fabsf",
-      [](Stack* stack, absl::Span<const BufferView> args,
-         absl::Span<BufferView> results) -> Status {
-        // TODO(benvanik): example native functions.
-        LOG(INFO) << "fabsf";
-        return OkStatus();
-      }));
+                   vm::SequencerModule::FromFile(std::move(main_module_file)));
 
   // Register the main module with the context.
   // We could add additional modules (specializations, shared libraries, etc).
   // ModuleFioles are stateless so we could have the same module_file used by
   // multiple contexts simultaneously.
-  auto* main_module_ptr = main_module.get();
-  RETURN_IF_ERROR(context.RegisterModule(std::move(main_module)));
+  RETURN_IF_ERROR(context->RegisterModule(add_ref(main_module)));
 
   // Dump the registered modules.
-  PrintModuleFlagBitfield print_flags = PrintModuleFlag::kNone;
-  if (absl::GetFlag(FLAGS_print_source_info)) {
-    print_flags |= PrintModuleFlag::kIncludeSourceMapping;
+  rt::PrintModuleFlagBitfield print_flags = rt::PrintModuleFlag::kNone;
+  if (absl::GetFlag(FLAGS_print_disassembly)) {
+    print_flags |= rt::PrintModuleFlag::kDisassemble;
   }
-  for (const auto& module : context.modules()) {
-    RETURN_IF_ERROR(PrintModuleToStream(sequencer_opcode_table(), *module,
-                                        print_flags, &std::cout));
+  for (const auto& module : context->modules()) {
+    RETURN_IF_ERROR(PrintModuleToStream(*module, print_flags, &std::cout));
   }
 
-  // Setup a new fiber.
-  FiberState fiber_state(instance);
-
-  // Setup arguments and storage for results.
-  Function main_function;
+  rt::Function main_function;
   if (!absl::GetFlag(FLAGS_main_function).empty()) {
     // User-specified main function.
-    ASSIGN_OR_RETURN(main_function,
-                     context.LookupExport(absl::GetFlag(FLAGS_main_function)));
+    ASSIGN_OR_RETURN(main_function, main_module->LookupFunctionByName(
+                                        rt::Function::Linkage::kExport,
+                                        absl::GetFlag(FLAGS_main_function)));
   } else {
     // No main function specified; to prevent non-deterministic behavior we
     // require one unless there's exactly one exported function in the module.
-    auto* exports = main_module_ptr->function_table().def().exports();
-    if (exports && exports->size() == 1) {
-      ASSIGN_OR_RETURN(
-          main_function,
-          main_module_ptr->function_table().LookupFunction(exports->Get(0)));
+    if (main_module->signature().export_function_count() == 1) {
+      ASSIGN_OR_RETURN(main_function, main_module->LookupFunctionByOrdinal(
+                                          rt::Function::Linkage::kExport, 0));
     } else {
       return InvalidArgumentErrorBuilder(IREE_LOC)
              << "--main_function= must be specified to disambiguate the "
                 "function to run";
     }
   }
-  ASSIGN_OR_RETURN(std::vector<BufferView> args,
-                   ParseInputsFromFlags(device->allocator()));
-  std::vector<BufferView> results;
-  results.resize(main_function.result_count());
 
   // Call into the main function.
-  RETURN_IF_ERROR(context.Invoke(&fiber_state, main_function,
-                                 absl::MakeSpan(args),
-                                 absl::MakeSpan(results)));
+  ASSIGN_OR_RETURN(auto arguments, ParseInputsFromFlags(device->allocator()));
+  ASSIGN_OR_RETURN(auto invocation,
+                   rt::Invocation::Create(add_ref(context), main_function,
+                                          make_ref<rt::Policy>(), {},
+                                          absl::MakeConstSpan(arguments)));
+
+  // Wait until invocation completes.
+  RETURN_IF_ERROR(invocation->Await(absl::InfiniteFuture()));
+  ASSIGN_OR_RETURN(auto results, invocation->ConsumeResults());
 
   // Dump all results to stdout.
   std::vector<std::string> output_types =
@@ -201,5 +180,4 @@
   return 0;
 }
 
-}  // namespace vm
 }  // namespace iree
diff --git a/iree/tools/web/BUILD b/iree/tools/web/BUILD
index 3737e05..f55ef9d 100644
--- a/iree/tools/web/BUILD
+++ b/iree/tools/web/BUILD
@@ -1,12 +1,12 @@
 # IREE web tools.
 
+load("//third_party/emscripten:split_transition_defs.bzl", "auto_wasm_binary")
+
 package(
     default_visibility = ["//visibility:public"],
     licenses = ["notice"],  # Apache 2.0
 )
 
-load("//third_party/emscripten:split_transition_defs.bzl", "auto_wasm_binary")
-
 EMSCRIPTEN_LINKOPTS_COMMON = [
     # Error at compile time on unresolved symbols.
     "-s ERROR_ON_UNDEFINED_SYMBOLS=1",
@@ -58,9 +58,8 @@
         "//iree/hal:buffer_view_string_util",
         "//iree/hal:driver_registry",
         "//iree/hal/interpreter:interpreter_driver_module",
-        "//iree/vm:bytecode_tables_sequencer",
-        "//iree/vm:instance",
-        "//iree/vm:sequencer_context",
+        "//iree/rt",
+        "//iree/vm:sequencer_module",
         "//third_party/emscripten:embind",
     ],
 )
diff --git a/iree/tools/web/run_module_emscripten.cc b/iree/tools/web/run_module_emscripten.cc
index f713807..49d88a7 100644
--- a/iree/tools/web/run_module_emscripten.cc
+++ b/iree/tools/web/run_module_emscripten.cc
@@ -26,14 +26,10 @@
 #include "iree/hal/buffer_view.h"
 #include "iree/hal/buffer_view_string_util.h"
 #include "iree/hal/driver_registry.h"
+#include "iree/rt/context.h"
+#include "iree/rt/instance.h"
 #include "iree/schemas/module_def_generated.h"
-#include "iree/vm/bytecode_tables_sequencer.h"
-#include "iree/vm/fiber_state.h"
-#include "iree/vm/function.h"
-#include "iree/vm/instance.h"
-#include "iree/vm/module.h"
-#include "iree/vm/module_printer.h"
-#include "iree/vm/sequencer_context.h"
+#include "iree/vm/sequencer_module.h"
 
 namespace iree {
 
@@ -59,52 +55,49 @@
 // Runs an IREE module with the provided inputs and returns its outputs.
 StatusOr<std::string> RunIreeModule(std::string module_file_data,
                                     absl::string_view inputs_string) {
-  auto instance = std::make_shared<vm::Instance>();
+  auto instance = make_ref<rt::Instance>();
 
   // Create driver and device.
   ASSIGN_OR_RETURN(auto driver, hal::DriverRegistry::shared_registry()->Create(
                                     "interpreter"));
   ASSIGN_OR_RETURN(auto device, driver->CreateDefaultDevice());
   RETURN_IF_ERROR(instance->device_manager()->RegisterDevice(device));
-  vm::SequencerContext context(instance);
+
+  auto policy = make_ref<rt::Policy>();
+  auto context = make_ref<rt::Context>(add_ref(instance), std::move(policy));
 
   // Load main module FlatBuffer.
   ASSIGN_OR_RETURN(auto main_module_file,
                    FlatBufferFile<ModuleDef>::FromString(ModuleDefIdentifier(),
                                                          module_file_data));
   ASSIGN_OR_RETURN(auto main_module,
-                   vm::Module::FromFile(std::move(main_module_file)));
+                   vm::SequencerModule::FromFile(std::move(main_module_file)));
 
   // Register the main module with the context.
-  RETURN_IF_ERROR(context.RegisterModule(std::move(main_module)));
-
-  // Dump the registered modules.
-  vm::PrintModuleFlagBitfield print_flags =
-      vm::PrintModuleFlag::kIncludeSourceMapping;
-  for (const auto& module : context.modules()) {
-    RETURN_IF_ERROR(vm::PrintModuleToStream(vm::sequencer_opcode_table(),
-                                            *module, print_flags, &std::cout));
-  }
-
-  // Setup a new fiber.
-  vm::FiberState fiber_state(instance);
+  RETURN_IF_ERROR(context->RegisterModule(add_ref(main_module)));
 
   // Setup arguments and storage for results.
-  // TODO(scotttodd): Receive main function name from JS
-  ASSIGN_OR_RETURN(vm::Function main_function, context.LookupExport("main"));
+  // TODO(scotttodd): Receive main function name from JS.
+  ASSIGN_OR_RETURN(auto main_function,
+                   main_module->LookupFunctionByName(
+                       rt::Function::Linkage::kExport, "main"));
 
-  ASSIGN_OR_RETURN(std::vector<hal::BufferView> args,
+  ASSIGN_OR_RETURN(auto arguments,
                    ParseInputs(inputs_string, device->allocator()));
-  std::vector<hal::BufferView> results;
-  results.resize(main_function.result_count());
 
   // Call into the main function.
-  RETURN_IF_ERROR(context.Invoke(&fiber_state, main_function,
-                                 absl::MakeSpan(args),
-                                 absl::MakeSpan(results)));
+  ASSIGN_OR_RETURN(auto invocation,
+                   rt::Invocation::Create(add_ref(context), main_function,
+                                          make_ref<rt::Policy>(), {},
+                                          absl::MakeConstSpan(arguments)));
+
+  // Wait until invocation completes.
+  // TODO(scotttodd): make this an async callback.
+  RETURN_IF_ERROR(invocation->Await(absl::InfiniteFuture()));
+  ASSIGN_OR_RETURN(auto results, invocation->ConsumeResults());
 
   // Dump all results to stdout.
-  // TODO(scotttodd): Receive output types / print mode from JS
+  // TODO(scotttodd): Receive output types / print mode from JS.
   // TODO(scotttodd): Return list of outputs instead of just the first (proto?)
   for (int i = 0; i < results.size(); ++i) {
     const auto& result = results[i];
diff --git a/iree/vm/BUILD b/iree/vm/BUILD
index d65e151..9da6c77 100644
--- a/iree/vm/BUILD
+++ b/iree/vm/BUILD
@@ -6,22 +6,30 @@
 )
 
 cc_library(
-    name = "bytecode_printer",
-    srcs = ["bytecode_printer.cc"],
-    hdrs = ["bytecode_printer.h"],
+    name = "bytecode_module",
+    srcs = [
+        "bytecode_disassembler.cc",
+        "bytecode_module.cc",
+    ],
+    hdrs = [
+        "bytecode_disassembler.h",
+        "bytecode_module.h",
+    ],
     deps = [
         ":bytecode_util",
-        ":executable_table",
-        ":function_table",
-        ":module",
         ":opcode_info",
-        ":source_map",
+        ":source_map_resolver",
         ":type",
+        "//iree/base:flatbuffer_util",
         "//iree/base:status",
+        "//iree/base:tracing",
+        "//iree/hal:buffer_view",
+        "//iree/rt",
         "//iree/schemas",
         "//iree/schemas/bytecode:bytecode_v0",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:inlined_vector",
+        "@com_google_absl//absl/memory",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/types:span",
     ],
@@ -32,13 +40,13 @@
     srcs = ["bytecode_reader.cc"],
     hdrs = ["bytecode_reader.h"],
     deps = [
-        ":function",
-        ":stack",
+        ":bytecode_module",
         ":type",
         "//iree/base:shape",
         "//iree/base:status",
         "//iree/hal:buffer_view",
         "//iree/hal:heap_buffer",
+        "//iree/rt",
         "//iree/schemas/bytecode:bytecode_v0",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:inlined_vector",
@@ -80,122 +88,13 @@
     srcs = ["bytecode_validator.cc"],
     hdrs = ["bytecode_validator.h"],
     deps = [
-        ":context",
-        ":module",
+        ":bytecode_module",
         "//iree/base:status",
         "//iree/schemas",
     ],
 )
 
 cc_library(
-    name = "context",
-    srcs = ["context.cc"],
-    hdrs = ["context.h"],
-    deps = [
-        ":function",
-        ":module",
-        "//iree/base:flatbuffer_util",
-        "//iree/base:status",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
-    ],
-)
-
-cc_library(
-    name = "executable_table",
-    srcs = ["executable_table.cc"],
-    hdrs = ["executable_table.h"],
-    deps = [
-        "//iree/base:flatbuffer_util",
-        "//iree/base:source_location",
-        "//iree/base:status",
-        "//iree/schemas",
-    ],
-)
-
-cc_library(
-    name = "fiber_state",
-    srcs = ["fiber_state.cc"],
-    hdrs = ["fiber_state.h"],
-    deps = [
-        ":instance",
-        ":stack",
-        "//iree/base:status",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
-    ],
-)
-
-cc_library(
-    name = "function",
-    srcs = ["function.cc"],
-    hdrs = ["function.h"],
-    deps = [
-        "//iree/base:flatbuffer_util",
-        "//iree/base:status",
-        "//iree/hal:buffer_view",
-        "//iree/schemas",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
-    ],
-)
-
-cc_library(
-    name = "function_table",
-    srcs = ["function_table.cc"],
-    hdrs = ["function_table.h"],
-    deps = [
-        ":function",
-        "//iree/base:flatbuffer_util",
-        "//iree/base:status",
-        "//iree/schemas",
-        "@com_google_absl//absl/container:flat_hash_map",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
-    ],
-)
-
-cc_library(
-    name = "instance",
-    srcs = ["instance.cc"],
-    hdrs = ["instance.h"],
-    deps = [
-        "//iree/base:source_location",
-        "//iree/base:status",
-        "//iree/hal:device_manager",
-        "@com_google_absl//absl/memory",
-    ],
-)
-
-cc_library(
-    name = "module",
-    srcs = ["module.cc"],
-    hdrs = ["module.h"],
-    deps = [
-        ":executable_table",
-        ":function_table",
-        "//iree/base:flatbuffer_util",
-        "//iree/base:status",
-        "//iree/schemas",
-        "@com_google_absl//absl/memory",
-    ],
-)
-
-cc_library(
-    name = "module_printer",
-    srcs = ["module_printer.cc"],
-    hdrs = ["module_printer.h"],
-    deps = [
-        ":bytecode_printer",
-        ":module",
-        ":opcode_info",
-        ":source_map",
-        "//iree/base:bitfield",
-        "//iree/base:status",
-    ],
-)
-
-cc_library(
     name = "opcode_info",
     hdrs = ["opcode_info.h"],
     deps = [
@@ -207,35 +106,15 @@
 )
 
 cc_library(
-    name = "sequencer_context",
-    srcs = ["sequencer_context.cc"],
-    hdrs = ["sequencer_context.h"],
-    deps = [
-        ":context",
-        ":fiber_state",
-        ":function",
-        ":instance",
-        ":module",
-        ":sequencer_dispatch",
-        "//iree/base:flatbuffer_util",
-        "//iree/base:status",
-        "//iree/hal:buffer_view",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
-    ],
-)
-
-cc_library(
     name = "sequencer_dispatch",
     srcs = ["sequencer_dispatch.cc"],
     hdrs = ["sequencer_dispatch.h"],
     deps = [
+        ":bytecode_module",
         ":bytecode_reader",
         ":bytecode_tables_sequencer",
         ":bytecode_util",
-        ":function",
         ":opcode_info",
-        ":stack",
         "//iree/base:logging",
         "//iree/base:memory",
         "//iree/base:status",
@@ -244,6 +123,7 @@
         "//iree/hal:device",
         "//iree/hal:device_placement",
         "//iree/hal:heap_buffer",
+        "//iree/rt",
         "//iree/schemas/bytecode:sequencer_bytecode_v0",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:inlined_vector",
@@ -254,35 +134,32 @@
 )
 
 cc_library(
-    name = "source_map",
-    srcs = ["source_map.cc"],
-    hdrs = ["source_map.h"],
+    name = "sequencer_module",
+    srcs = ["sequencer_module.cc"],
+    hdrs = ["sequencer_module.h"],
     deps = [
-        "//iree/base:flatbuffer_util",
+        ":bytecode_module",
+        ":bytecode_tables_sequencer",
+        ":sequencer_dispatch",
         "//iree/base:status",
-        "//iree/schemas",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:optional",
+        "//iree/base:tracing",
+        "//iree/hal:buffer_view",
+        "//iree/rt",
+        "@com_google_absl//absl/memory",
     ],
 )
 
 cc_library(
-    name = "stack",
-    srcs = [
-        "stack.cc",
-        "stack_frame.cc",
-    ],
-    hdrs = [
-        "stack.h",
-        "stack_frame.h",
-    ],
+    name = "source_map_resolver",
+    srcs = ["source_map_resolver.cc"],
+    hdrs = ["source_map_resolver.h"],
     deps = [
-        ":function",
-        ":module",
+        "//iree/base:flatbuffer_util",
         "//iree/base:status",
-        "//iree/hal:buffer_view",
+        "//iree/rt",
+        "//iree/schemas",
         "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
+        "@com_google_absl//absl/types:optional",
     ],
 )
 
diff --git a/iree/vm/CMakeLists.txt b/iree/vm/CMakeLists.txt
index 4a357f3..ab8c873 100644
--- a/iree/vm/CMakeLists.txt
+++ b/iree/vm/CMakeLists.txt
@@ -12,14 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 iree_cc_library(
   NAME
-    bytecode_printer
+    bytecode_disassembler
   SRCS
-    "bytecode_printer.cc"
+    "bytecode_disassembler.cc"
   HDRS
-    "bytecode_printer.h"
+    "bytecode_disassembler.h"
   DEPS
     absl::core_headers
     absl::inlined_vector
@@ -28,18 +27,34 @@
     iree::base::status
     iree::schemas
     iree::schemas::bytecode::bytecode_v0
+    iree::rt
     iree::vm::bytecode_util
-    iree::vm::executable_table
-    iree::vm::function_table
-    iree::vm::module
     iree::vm::opcode_info
-    iree::vm::source_map
+    iree::vm::source_map_resolver
     iree::vm::type
   PUBLIC
 )
 
 iree_cc_library(
   NAME
+    bytecode_module
+  SRCS
+    "bytecode_module.cc"
+  HDRS
+    "bytecode_module.h"
+  DEPS
+    absl::memory
+    iree::base::flatbuffer_util
+    iree::base::status
+    iree::rt
+    iree::schemas
+    iree::vm::bytecode_disassembler
+    iree::vm::source_map_resolver
+  PUBLIC
+)
+
+iree_cc_library(
+  NAME
     bytecode_reader
   SRCS
     "bytecode_reader.cc"
@@ -52,9 +67,9 @@
     iree::base::status
     iree::hal::buffer_view
     iree::hal::heap_buffer
+    iree::rt
     iree::schemas::bytecode::bytecode_v0
-    iree::vm::function
-    iree::vm::stack
+    iree::vm::bytecode_module
     iree::vm::type
   PUBLIC
 )
@@ -115,137 +130,6 @@
 
 iree_cc_library(
   NAME
-    context
-  SRCS
-    "context.cc"
-  HDRS
-    "context.h"
-  DEPS
-    absl::strings
-    absl::span
-    iree::base::flatbuffer_util
-    iree::base::status
-    iree::vm::function
-    iree::vm::module
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
-    executable_table
-  SRCS
-    "executable_table.cc"
-  HDRS
-    "executable_table.h"
-  DEPS
-    iree::base::flatbuffer_util
-    iree::base::status
-    iree::schemas
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
-    fiber_state
-  SRCS
-    "fiber_state.cc"
-  HDRS
-    "fiber_state.h"
-  DEPS
-    absl::strings
-    absl::span
-    iree::base::status
-    iree::vm::instance
-    iree::vm::stack
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
-    function
-  SRCS
-    "function.cc"
-  HDRS
-    "function.h"
-  DEPS
-    absl::strings
-    absl::span
-    iree::base::flatbuffer_util
-    iree::base::status
-    iree::hal::buffer_view
-    iree::schemas
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
-    function_table
-  SRCS
-    "function_table.cc"
-  HDRS
-    "function_table.h"
-  DEPS
-    absl::flat_hash_map
-    absl::strings
-    absl::span
-    iree::base::flatbuffer_util
-    iree::base::status
-    iree::schemas
-    iree::vm::function
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
-    instance
-  SRCS
-    "instance.cc"
-  HDRS
-    "instance.h"
-  DEPS
-    absl::memory
-    iree::base::source_location
-    iree::base::status
-    iree::hal::device_manager
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
-    module
-  SRCS
-    "module.cc"
-  HDRS
-    "module.h"
-  DEPS
-    absl::memory
-    iree::base::flatbuffer_util
-    iree::base::status
-    iree::schemas
-    iree::vm::executable_table
-    iree::vm::function_table
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
-    module_printer
-  SRCS
-    "module_printer.cc"
-  HDRS
-    "module_printer.h"
-  DEPS
-    iree::base::bitfield
-    iree::base::status
-    iree::vm::bytecode_printer
-    iree::vm::module
-    iree::vm::opcode_info
-    iree::vm::source_map
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
     opcode_info
   HDRS
     "opcode_info.h"
@@ -259,28 +143,6 @@
 
 iree_cc_library(
   NAME
-    sequencer_context
-  SRCS
-    "sequencer_context.cc"
-  HDRS
-    "sequencer_context.h"
-  DEPS
-    absl::strings
-    absl::span
-    iree::base::flatbuffer_util
-    iree::base::status
-    iree::hal::buffer_view
-    iree::vm::context
-    iree::vm::fiber_state
-    iree::vm::function
-    iree::vm::instance
-    iree::vm::module
-    iree::vm::sequencer_dispatch
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
     sequencer_dispatch
   SRCS
     "sequencer_dispatch.cc"
@@ -300,53 +162,53 @@
     iree::hal::device
     iree::hal::device_placement
     iree::hal::heap_buffer
+    iree::rt
     iree::schemas::bytecode::sequencer_bytecode_v0
+    iree::vm::bytecode_module
     iree::vm::bytecode_reader
     iree::vm::bytecode_tables_sequencer
     iree::vm::bytecode_util
-    iree::vm::function
     iree::vm::opcode_info
-    iree::vm::stack
   PUBLIC
 )
 
 iree_cc_library(
   NAME
-    source_map
+    sequencer_module
   SRCS
-    "source_map.cc"
+    "sequencer_module.cc"
   HDRS
-    "source_map.h"
+    "sequencer_module.h"
+  DEPS
+    absl::core_headers
+    iree::base::status
+    iree::hal::buffer_view
+    iree::rt
+    iree::schemas::bytecode::sequencer_bytecode_v0
+    iree::vm::bytecode_module
+    iree::vm::sequencer_dispatch
+  PUBLIC
+)
+
+iree_cc_library(
+  NAME
+    source_map_resolver
+  SRCS
+    "source_map_resolver.cc"
+  HDRS
+    "source_map_resolver.h"
   DEPS
     absl::strings
     absl::optional
     iree::base::flatbuffer_util
     iree::base::status
+    iree::rt
     iree::schemas
   PUBLIC
 )
 
 iree_cc_library(
   NAME
-    stack
-  SRCS
-    "stack.cc"
-    "stack_frame.cc"
-  HDRS
-    "stack.h"
-    "stack_frame.h"
-  DEPS
-    absl::strings
-    absl::span
-    iree::base::status
-    iree::hal::buffer_view
-    iree::vm::function
-    iree::vm::module
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
     type
   SRCS
     "type.cc"
diff --git a/iree/vm/bytecode_printer.cc b/iree/vm/bytecode_disassembler.cc
similarity index 73%
rename from iree/vm/bytecode_printer.cc
rename to iree/vm/bytecode_disassembler.cc
index 136d257..4868792 100644
--- a/iree/vm/bytecode_printer.cc
+++ b/iree/vm/bytecode_disassembler.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "iree/vm/bytecode_printer.h"
+#include "iree/vm/bytecode_disassembler.h"
 
 #include <iomanip>
 #include <sstream>
@@ -25,8 +25,8 @@
 #include "iree/base/status.h"
 #include "iree/schemas/bytecode/bytecode_v0.h"
 #include "iree/schemas/source_map_def_generated.h"
+#include "iree/vm/bytecode_module.h"
 #include "iree/vm/bytecode_util.h"
-#include "iree/vm/module.h"
 #include "iree/vm/type.h"
 
 namespace iree {
@@ -34,8 +34,10 @@
 
 namespace {
 
+using ::iree::rt::SourceOffset;
+
 template <typename T>
-StatusOr<T> ReadValue(absl::Span<const uint8_t> data, int* offset) {
+StatusOr<T> ReadValue(absl::Span<const uint8_t> data, SourceOffset* offset) {
   if (*offset + sizeof(T) > data.size()) {
     return OutOfRangeErrorBuilder(IREE_LOC) << "Bytecode data underrun";
   }
@@ -44,16 +46,19 @@
   return value;
 }
 
-StatusOr<const Type> ReadType(absl::Span<const uint8_t> data, int* offset) {
+StatusOr<const Type> ReadType(absl::Span<const uint8_t> data,
+                              SourceOffset* offset) {
   ASSIGN_OR_RETURN(uint8_t type_index, ReadValue<uint8_t>(data, offset));
   return Type::FromTypeIndex(type_index);
 }
 
-StatusOr<uint8_t> ReadCount(absl::Span<const uint8_t> data, int* offset) {
+StatusOr<uint8_t> ReadCount(absl::Span<const uint8_t> data,
+                            SourceOffset* offset) {
   return ReadValue<uint8_t>(data, offset);
 }
 
-StatusOr<uint16_t> ReadValueSlot(absl::Span<const uint8_t> data, int* offset) {
+StatusOr<uint16_t> ReadValueSlot(absl::Span<const uint8_t> data,
+                                 SourceOffset* offset) {
   return ReadValue<uint16_t>(data, offset);
 }
 
@@ -103,55 +108,30 @@
 
 }  // namespace
 
-// static
-std::string BytecodePrinter::ToString(
-    OpcodeTable opcode_table, const FunctionTable& function_table,
-    const ExecutableTable& executable_table,
-    const SourceMapResolver& source_map_resolver,
-    const BytecodeDef& bytecode_def) {
-  BytecodePrinter printer(opcode_table, function_table, executable_table,
-                          source_map_resolver);
-  auto result = printer.Print(bytecode_def);
-  if (!result.ok()) {
-    return result.status().ToString();
-  }
-  return result.ValueOrDie();
-}
+StatusOr<std::vector<rt::Instruction>>
+BytecodeDisassembler::DisassembleInstructions(const rt::Function& function,
+                                              SourceOffset offset,
+                                              int32_t instruction_count) const {
+  std::vector<rt::Instruction> instructions;
 
-StatusOr<std::string> BytecodePrinter::Print(
-    const BytecodeDef& bytecode_def) const {
-  std::ostringstream stream;
-  RETURN_IF_ERROR(PrintToStream(bytecode_def, &stream)) << stream.str();
-  return stream.str();
-}
-
-Status BytecodePrinter::PrintToStream(const BytecodeDef& bytecode_def,
-                                      std::ostream* stream) const {
-  if (!bytecode_def.contents()) {
-    return OkStatus();
+  ASSIGN_OR_RETURN(
+      auto* function_def,
+      static_cast<const BytecodeModule*>(function.module())
+          ->GetFunctionDef(function.linkage(), function.ordinal()));
+  auto* bytecode_def = function_def->bytecode();
+  if (!bytecode_def) {
+    return UnavailableErrorBuilder(IREE_LOC) << "Function contains no body";
   }
   auto data = absl::MakeSpan(
-      reinterpret_cast<const uint8_t*>(bytecode_def.contents()->data()),
-      bytecode_def.contents()->size());
-  return PrintToStream(data, stream);
-}
+      reinterpret_cast<const uint8_t*>(bytecode_def->contents()->data()),
+      bytecode_def->contents()->size());
 
-Status BytecodePrinter::PrintToStream(absl::Span<const uint8_t> data,
-                                      std::ostream* stream) const {
   // TODO(benvanik): scan and find all branch offsets to insert labels
 
-  int offset = 0;
-  absl::optional<SourceLocation> previous_location;
-  while (offset < data.length()) {
-    auto source_location = source_map_resolver_.ResolveBytecodeOffset(offset);
-    if (source_location.has_value()) {
-      if (previous_location != source_location) {
-        *stream << std::setw(10) << "; " << source_location.value() << "\n";
-      }
-      previous_location = source_location;
-    }
-
-    *stream << std::setw(6) << offset << ": ";
+  while (offset < data.length() && instructions.size() < instruction_count) {
+    instructions.push_back({});
+    auto& instruction = instructions.back();
+    instruction.offset = offset;
 
     uint8_t opcode = data[offset++];
     const auto& opcode_info = GetOpcodeInfo(opcode_table_, opcode);
@@ -161,6 +141,8 @@
     }
     int payload_offset = offset;
 
+    std::ostringstream stream;
+
     // Print out return values, if any.
     int base_result_index = 0;
     int printed_result_count = 0;
@@ -168,7 +150,7 @@
          ++i) {
       if (opcode_info.operands[i] == OperandEncoding::kNone) break;
       if (printed_result_count > 0) {
-        *stream << ", ";
+        stream << ", ";
       }
       switch (opcode_info.operands[i]) {
         default:
@@ -193,20 +175,20 @@
         case OperandEncoding::kResultSlot: {
           ++printed_result_count;
           ASSIGN_OR_RETURN(uint16_t slot_ordinal, ReadValueSlot(data, &offset));
-          *stream << "%" << slot_ordinal;
+          stream << "%" << slot_ordinal;
           break;
         }
         case OperandEncoding::kVariadicResultSlots: {
           ++printed_result_count;
-          *stream << "[";
+          stream << "[";
           ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
           for (int j = 0; j < count; ++j) {
             ASSIGN_OR_RETURN(uint16_t slot_ordinal,
                              ReadValueSlot(data, &offset));
-            if (j > 0) *stream << ", ";
-            *stream << "%" << slot_ordinal;
+            if (j > 0) stream << ", ";
+            stream << "%" << slot_ordinal;
           }
-          *stream << "]";
+          stream << "]";
           break;
         }
         case OperandEncoding::kVariadicTransferSlots: {
@@ -268,11 +250,11 @@
       }
     }
     if (printed_result_count > 0) {
-      *stream << " = ";
+      stream << " = ";
     }
     offset = payload_offset;
 
-    *stream << opcode_info.mnemonic;
+    stream << opcode_info.mnemonic;
 
     // Print out operands.
     int base_operand_index = 0;
@@ -283,9 +265,9 @@
       if (opcode_info.operands[i] != OperandEncoding::kResultSlot &&
           opcode_info.operands[i] != OperandEncoding::kVariadicResultSlots) {
         if (i == base_operand_index) {
-          *stream << " ";
+          stream << " ";
         } else if (printed_operand_count > 0) {
-          *stream << ", ";
+          stream << ", ";
         }
       }
       switch (opcode_info.operands[i]) {
@@ -298,41 +280,41 @@
         case OperandEncoding::kInputSlot: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(uint16_t slot_ordinal, ReadValueSlot(data, &offset));
-          *stream << "%" << slot_ordinal;
+          stream << "%" << slot_ordinal;
           break;
         }
         case OperandEncoding::kVariadicInputSlots: {
           ++printed_operand_count;
-          *stream << "[";
+          stream << "[";
           ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
           for (int j = 0; j < count; ++j) {
             ASSIGN_OR_RETURN(uint16_t slot_ordinal,
                              ReadValueSlot(data, &offset));
-            if (j > 0) *stream << ", ";
-            *stream << "%" << slot_ordinal;
+            if (j > 0) stream << ", ";
+            stream << "%" << slot_ordinal;
           }
-          *stream << "]";
+          stream << "]";
           break;
         }
         case OperandEncoding::kOutputSlot: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(uint16_t slot_ordinal, ReadValueSlot(data, &offset));
-          *stream << "&"
-                  << "%" << slot_ordinal;
+          stream << "&"
+                 << "%" << slot_ordinal;
           break;
         }
         case OperandEncoding::kVariadicOutputSlots: {
           ++printed_operand_count;
-          *stream << "[";
+          stream << "[";
           ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
           for (int j = 0; j < count; ++j) {
             ASSIGN_OR_RETURN(uint16_t slot_ordinal,
                              ReadValueSlot(data, &offset));
-            if (j > 0) *stream << ", ";
-            *stream << "&"
-                    << "%" << slot_ordinal;
+            if (j > 0) stream << ", ";
+            stream << "&"
+                   << "%" << slot_ordinal;
           }
-          *stream << "]";
+          stream << "]";
           break;
         }
         case OperandEncoding::kResultSlot: {
@@ -348,17 +330,17 @@
         }
         case OperandEncoding::kVariadicTransferSlots: {
           ++printed_operand_count;
-          *stream << "[";
+          stream << "[";
           ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
           for (int j = 0; j < count; ++j) {
             ASSIGN_OR_RETURN(uint16_t src_slot_ordinal,
                              ReadValueSlot(data, &offset));
             ASSIGN_OR_RETURN(uint16_t dst_slot_ordinal,
                              ReadValueSlot(data, &offset));
-            if (j > 0) *stream << ", ";
-            *stream << "%" << src_slot_ordinal << "=>%" << dst_slot_ordinal;
+            if (j > 0) stream << ", ";
+            stream << "%" << src_slot_ordinal << "=>%" << dst_slot_ordinal;
           }
-          *stream << "]";
+          stream << "]";
           break;
         }
         case OperandEncoding::kConstant: {
@@ -374,7 +356,7 @@
           }
           ASSIGN_OR_RETURN(auto encoding,
                            ReadValue<ConstantEncoding>(data, &offset));
-          *stream << ConstantEncodingToString(encoding);
+          stream << ConstantEncodingToString(encoding);
           int serialized_element_count = 1;
           switch (encoding) {
             case ConstantEncoding::kDense:
@@ -388,27 +370,29 @@
                      << "Unimplemented constant encoding "
                      << static_cast<int>(encoding);
           }
-          *stream << " buffer_view<";
+          stream << " buffer_view<";
           if (!shape.empty()) {
-            *stream << absl::StrJoin(shape, "x") << "x";
+            stream << absl::StrJoin(shape, "x") << "x";
           }
-          *stream << type << ">{";
+          stream << type << ">{";
           size_t element_size = type.element_size();
           auto bytes = data.subspan(
               offset, std::min(serialized_element_count, 1024) * element_size);
-          *stream << ConstantToString(type, bytes);
-          if (serialized_element_count > 1024) *stream << "...";
+          stream << ConstantToString(type, bytes);
+          if (serialized_element_count > 1024) stream << "...";
           offset += serialized_element_count * element_size;
-          *stream << "}";
+          stream << "}";
           break;
         }
         case OperandEncoding::kFunctionOrdinal: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(auto function_ordinal,
                            ReadValue<uint32_t>(data, &offset));
-          ASSIGN_OR_RETURN(auto function,
-                           function_table_.LookupFunction(function_ordinal));
-          *stream << "@" << function_ordinal << " " << function.name();
+          ASSIGN_OR_RETURN(
+              auto target_function,
+              function.module()->LookupFunctionByOrdinal(
+                  rt::Function::Linkage::kInternal, function_ordinal));
+          stream << "@" << function_ordinal << " " << target_function.name();
           break;
         }
         case OperandEncoding::kDispatchOrdinal: {
@@ -418,88 +402,80 @@
           ASSIGN_OR_RETURN(auto export_ordinal,
                            ReadValue<uint16_t>(data, &offset));
           // TODO(benvanik): lookup in executable table.
-          *stream << "@" << dispatch_ordinal << ":" << export_ordinal;
+          stream << "@" << dispatch_ordinal << ":" << export_ordinal;
           break;
         }
         case OperandEncoding::kImportOrdinal: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(auto import_ordinal,
                            ReadValue<uint32_t>(data, &offset));
-          ASSIGN_OR_RETURN(auto* function,
-                           function_table_.LookupImport(import_ordinal));
-          *stream << "@i" << import_ordinal << " ";
-          switch (function->link_type()) {
-            default:
-              *stream << "??";
-              break;
-            case ImportFunction::LinkType::kNativeFunction:
-              *stream << "<native>";
-              break;
-            case ImportFunction::LinkType::kModule:
-              *stream << function->linked_function().module().name() << ":"
-                      << function->linked_function().name();
-              break;
-          }
+          ASSIGN_OR_RETURN(auto target_function,
+                           function.module()->LookupFunctionByOrdinal(
+                               rt::Function::Linkage::kImport, import_ordinal));
+          stream << "@i" << import_ordinal << " " << target_function.name();
           break;
         }
         case OperandEncoding::kBlockOffset: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(uint32_t block_offset,
                            ReadValue<uint32_t>(data, &offset));
-          *stream << ":" << block_offset;
+          stream << ":" << block_offset;
           break;
         }
         case OperandEncoding::kTypeIndex: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(auto type, ReadType(data, &offset));
-          *stream << type;
+          stream << type;
           break;
         }
         case OperandEncoding::kIndex: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(auto index, ReadValue<int32_t>(data, &offset));
-          *stream << "#" << index;
+          stream << "#" << index;
           break;
         }
         case OperandEncoding::kIndexList: {
           ++printed_operand_count;
-          *stream << "{";
+          stream << "{";
           ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
           for (int j = 0; j < count; ++j) {
             ASSIGN_OR_RETURN(auto dim, ReadValue<int32_t>(data, &offset));
-            if (j > 0) *stream << ",";
-            *stream << dim;
+            if (j > 0) stream << ",";
+            stream << dim;
           }
-          *stream << "}";
+          stream << "}";
           break;
         }
         case OperandEncoding::kCmpIPredicate: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(auto predicate_value,
                            ReadValue<uint8_t>(data, &offset));
-          *stream << "<"
-                  << PredicateToString(
-                         static_cast<CmpIPredicate>(predicate_value))
-                  << ">";
+          stream << "<"
+                 << PredicateToString(
+                        static_cast<CmpIPredicate>(predicate_value))
+                 << ">";
           break;
         }
         case OperandEncoding::kCmpFPredicate: {
           ++printed_operand_count;
           ASSIGN_OR_RETURN(auto predicate_value,
                            ReadValue<uint8_t>(data, &offset));
-          *stream << "<"
-                  << PredicateToString(
-                         static_cast<CmpFPredicate>(predicate_value))
-                  << ">";
+          stream << "<"
+                 << PredicateToString(
+                        static_cast<CmpFPredicate>(predicate_value))
+                 << ">";
           break;
         }
       }
     }
 
-    *stream << "\n";
+    stream << "\n";
+
+    instruction.long_text = stream.str();
+    instruction.short_text = instruction.long_text;
   }
 
-  return OkStatus();
+  return instructions;
 }
 
 }  // namespace vm
diff --git a/iree/vm/bytecode_disassembler.h b/iree/vm/bytecode_disassembler.h
new file mode 100644
index 0000000..633e290
--- /dev/null
+++ b/iree/vm/bytecode_disassembler.h
@@ -0,0 +1,46 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_VM_BYTECODE_DISASSEMBLER_H_
+#define IREE_VM_BYTECODE_DISASSEMBLER_H_
+
+#include <ostream>
+
+#include "iree/base/status.h"
+#include "iree/rt/disassembler.h"
+#include "iree/schemas/bytecode_def_generated.h"
+#include "iree/schemas/source_map_def_generated.h"
+#include "iree/vm/opcode_info.h"
+
+namespace iree {
+namespace vm {
+
+// Disassembles bytecode with a specific op set to text.
+class BytecodeDisassembler final : public rt::Disassembler {
+ public:
+  explicit BytecodeDisassembler(OpcodeTable opcode_table)
+      : opcode_table_(opcode_table) {}
+
+  StatusOr<std::vector<rt::Instruction>> DisassembleInstructions(
+      const rt::Function& function, rt::SourceOffset offset,
+      int32_t instruction_count) const override;
+
+ private:
+  OpcodeTable opcode_table_;
+};
+
+}  // namespace vm
+}  // namespace iree
+
+#endif  // IREE_VM_BYTECODE_DISASSEMBLER_H_
diff --git a/iree/vm/bytecode_module.cc b/iree/vm/bytecode_module.cc
new file mode 100644
index 0000000..7812cb6
--- /dev/null
+++ b/iree/vm/bytecode_module.cc
@@ -0,0 +1,310 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "iree/vm/bytecode_module.h"
+
+#include "absl/memory/memory.h"
+#include "iree/base/status.h"
+#include "iree/base/tracing.h"
+#include "iree/hal/buffer_view.h"
+#include "iree/vm/bytecode_disassembler.h"
+
+namespace iree {
+namespace vm {
+
+namespace {
+
+using ::iree::hal::BufferView;
+using ::iree::rt::Function;
+using ::iree::rt::FunctionSignature;
+using ::iree::rt::Module;
+using ::iree::rt::ModuleSignature;
+
+Status ValidateElementSize(int element_bit_width,
+                           const ElementTypeDef& expected_element_type) {
+  switch (expected_element_type.type_union_type()) {
+    case ElementTypeDefUnion::FloatTypeDef: {
+      auto expected_bit_width =
+          expected_element_type.type_union_as_FloatTypeDef()->width();
+      if (element_bit_width != expected_bit_width) {
+        return InvalidArgumentErrorBuilder(IREE_LOC)
+               << "Has element bit width " << element_bit_width
+               << " but expected " << expected_bit_width;
+      }
+      return OkStatus();
+    }
+    case ElementTypeDefUnion::IntegerTypeDef: {
+      auto expected_bit_width =
+          expected_element_type.type_union_as_IntegerTypeDef()->width();
+      if (element_bit_width != expected_bit_width) {
+        return InvalidArgumentErrorBuilder(IREE_LOC)
+               << "Has element bit width " << element_bit_width
+               << " but expected " << expected_bit_width;
+      }
+      return OkStatus();
+    }
+    case ElementTypeDefUnion::UnknownTypeDef:
+    case ElementTypeDefUnion::NONE: {
+    }
+  }
+  return InvalidArgumentErrorBuilder(IREE_LOC)
+         << "Defined type has unsupported element type "
+         << EnumNameElementTypeDefUnion(
+                expected_element_type.type_union_type());
+}
+
+Status ValidateTypeStructure(const FunctionTypeDef& type_def) {
+  // Ensure all fields are populated.
+  return OkStatus();
+}
+
+Status ValidateFunctionTableStructure(
+    const FunctionTableDef& function_table_def) {
+  if (!function_table_def.functions()) {
+    return InvalidArgumentErrorBuilder(IREE_LOC)
+           << "Function table is missing the function listing";
+  }
+
+  // All functions must contain a valid type.
+  const auto& functions = *function_table_def.functions();
+  for (int i = 0; i < functions.size(); ++i) {
+    const auto* function = functions[i];
+    if (!function) {
+      return InvalidArgumentErrorBuilder(IREE_LOC)
+             << "Function ordinal " << i << " is missing its contents";
+    }
+    if (!function->type()) {
+      return InvalidArgumentErrorBuilder(IREE_LOC)
+             << "Function ordinal " << i << " is missing its type";
+    }
+    RETURN_IF_ERROR(ValidateTypeStructure(*function->type()));
+  }
+
+  // Imports must also have a name (that we can use to resolve it).
+  if (function_table_def.imports()) {
+    const auto& imports = *function_table_def.imports();
+    for (int i = 0; i < imports.size(); ++i) {
+      int function_index = imports[i];
+      if (!functions[function_index]->name()) {
+        return InvalidArgumentErrorBuilder(IREE_LOC)
+               << "Import ordinal " << i << " is missing its contents";
+      }
+    }
+  }
+
+  // Exports must also have a name (that others will use to look it up).
+  if (function_table_def.exports()) {
+    const auto& exports = *function_table_def.exports();
+    for (int i = 0; i < exports.size(); ++i) {
+      int function_index = exports[i];
+      if (!functions[function_index]->name()) {
+        return InvalidArgumentErrorBuilder(IREE_LOC)
+               << "Export ordinal " << i << " is missing its contents";
+      }
+    }
+  }
+
+  return OkStatus();
+}
+
+Status ValidateExecutableTableStructure(
+    const ExecutableTableDef& executable_table_def) {
+  if (!executable_table_def.multi_arch_executables()) {
+    // May have sequencer only fns. Fine to not have dispatchable executables.
+    return OkStatus();
+  }
+
+  // All fat executables need at least one device-specific executable.
+  const auto& multi_arch_executables =
+      *executable_table_def.multi_arch_executables();
+  for (int i = 0; i < multi_arch_executables.size(); ++i) {
+    const auto* multi_arch_executable = multi_arch_executables[i];
+    if (!multi_arch_executable || !multi_arch_executable->executables() ||
+        multi_arch_executable->executables()->size() == 0) {
+      return InvalidArgumentErrorBuilder(IREE_LOC)
+             << "Multi-arch executable ordinal " << i
+             << " is missing its contents";
+    }
+  }
+
+  return OkStatus();
+}
+
+}  // namespace
+
+// static
+Status BytecodeModule::ValidateStructure(const ModuleDef& module_def) {
+  IREE_TRACE_SCOPE0("BytecodeModule::ValidateStructure");
+
+  // Must have a function table.
+  if (module_def.function_table()) {
+    RETURN_IF_ERROR(
+        ValidateFunctionTableStructure(*module_def.function_table()));
+  } else {
+    return InvalidArgumentErrorBuilder(IREE_LOC)
+           << "ModuleDef is missing a function table";
+  }
+
+  // Must have an executable table.
+  if (module_def.executable_table()) {
+    RETURN_IF_ERROR(
+        ValidateExecutableTableStructure(*module_def.executable_table()));
+  } else {
+    return InvalidArgumentErrorBuilder(IREE_LOC)
+           << "ModuleDef is missing an executable table";
+  }
+
+  return OkStatus();
+}
+
+BytecodeModule::BytecodeModule(std::unique_ptr<ModuleFile> module_file,
+                               OpcodeTable opcode_table)
+    : module_file_(std::move(module_file)),
+      module_def_(*module_file_->root()),
+      source_resolver_(SourceMapResolver::FromModule(module_def_)),
+      disassembler_(absl::make_unique<BytecodeDisassembler>(opcode_table)) {}
+
+BytecodeModule::~BytecodeModule() = default;
+
+const ModuleSignature BytecodeModule::signature() const {
+  return ModuleSignature(function_table_def().imports()->size(),
+                         function_table_def().exports()->size(),
+                         function_table_def().functions()->size(), 0);
+}
+
+std::string BytecodeModule::DebugStringShort() const {
+  return std::string(name());
+}
+
+StatusOr<int32_t> BytecodeModule::MapFunctionOrdinal(Function::Linkage linkage,
+                                                     int32_t ordinal) const {
+  const auto& function_table = function_table_def();
+  switch (linkage) {
+    case Function::Linkage::kImport:
+      if (ordinal < 0 || ordinal >= function_table.imports()->size()) {
+        return InvalidArgumentErrorBuilder(IREE_LOC)
+               << "Import ordinal " << ordinal
+               << " is outside the valid range [0, "
+               << function_table.imports()->size() << ")";
+      }
+      ordinal = function_table.imports()->Get(ordinal);
+      break;
+    case Function::Linkage::kExport:
+      if (ordinal < 0 || ordinal >= function_table.exports()->size()) {
+        return InvalidArgumentErrorBuilder(IREE_LOC)
+               << "Export ordinal " << ordinal
+               << " is outside the valid range [0, "
+               << function_table.exports()->size() << ")";
+      }
+      ordinal = function_table.exports()->Get(ordinal);
+      break;
+    default:
+      break;
+  }
+  if (ordinal < 0 || ordinal >= function_table.functions()->size()) {
+    return OutOfRangeErrorBuilder(IREE_LOC)
+           << "Function ordinal " << ordinal
+           << " is outside the valid range [0, "
+           << function_table.functions()->size() << ")";
+  }
+  return ordinal;
+}
+
+StatusOr<const Function> BytecodeModule::LookupFunctionByOrdinal(
+    Function::Linkage linkage, int32_t ordinal) const {
+  ASSIGN_OR_RETURN(ordinal, MapFunctionOrdinal(linkage, ordinal));
+  return Function(this, Function::Linkage::kInternal, ordinal);
+}
+
+StatusOr<const Function> BytecodeModule::LookupFunctionByName(
+    Function::Linkage linkage, absl::string_view name) const {
+  const auto& functions = *function_table_def().functions();
+  for (int i = 0; i < functions.size(); ++i) {
+    const auto* function_def = functions.Get(i);
+    if (WrapString(function_def->name()) == name) {
+      return LookupFunctionByOrdinal(Function::Linkage::kInternal, i);
+    }
+  }
+  return NotFoundErrorBuilder(IREE_LOC)
+         << "Function '" << name
+         << "' not found in function table (or names have been stripped)";
+}
+
+StatusOr<absl::string_view> BytecodeModule::GetFunctionName(
+    Function::Linkage linkage, int32_t ordinal) const {
+  ASSIGN_OR_RETURN(ordinal, MapFunctionOrdinal(linkage, ordinal));
+  const auto* function_def = function_table_def().functions()->Get(ordinal);
+  return WrapString(function_def->name());
+}
+
+StatusOr<const FunctionSignature> BytecodeModule::GetFunctionSignature(
+    Function::Linkage linkage, int32_t ordinal) const {
+  ASSIGN_OR_RETURN(ordinal, MapFunctionOrdinal(linkage, ordinal));
+  const auto* function_def = function_table_def().functions()->Get(ordinal);
+  const auto* type_def = function_def->type();
+  return FunctionSignature(
+      type_def->inputs() ? type_def->inputs()->size() : 0,
+      type_def->results() ? type_def->results()->size() : 0);
+}
+
+StatusOr<const FunctionDef*> BytecodeModule::GetFunctionDef(
+    rt::Function::Linkage linkage, int32_t ordinal) const {
+  ASSIGN_OR_RETURN(ordinal, MapFunctionOrdinal(linkage, ordinal));
+  const auto& function_defs = *function_table_def().functions();
+  if (ordinal >= function_defs.size()) {
+    return OutOfRangeErrorBuilder(IREE_LOC)
+           << "Internal function ordinal " << ordinal
+           << " out of range of table (" << function_defs.size() << ")";
+  }
+  return function_defs.Get(ordinal);
+}
+
+StatusOr<const MultiArchExecutableDef*>
+BytecodeModule::LookupMultiArchExecutable(int executable_ordinal) const {
+  if (executable_ordinal < 0 ||
+      executable_ordinal >=
+          executable_table_def().multi_arch_executables()->size()) {
+    return InvalidArgumentErrorBuilder(IREE_LOC)
+           << "Invalid multi-arch executable ordinal " << executable_ordinal;
+  }
+  return executable_table_def().multi_arch_executables()->Get(
+      executable_ordinal);
+}
+
+// static
+Status BytecodeModule::ValidateArgType(const BufferView& arg,
+                                       const MemRefTypeDef& expected_type) {
+  RETURN_IF_ERROR(
+      ValidateElementSize(arg.element_size * 8, *expected_type.element_type()));
+
+  auto expected_shape = expected_type.shape();
+  if (arg.shape.size() != expected_shape->size()) {
+    return InvalidArgumentErrorBuilder(IREE_LOC)
+           << "Argument should have rank " << expected_shape->size()
+           << " but has rank " << arg.shape.size();
+  }
+  for (int i = 0; i < expected_shape->size(); ++i) {
+    auto dim_size = arg.shape[i];
+    auto expected_dim_size = expected_shape->Get(i);
+    if (dim_size != expected_dim_size) {
+      return InvalidArgumentErrorBuilder(IREE_LOC)
+             << "Argument dimension " << i << " should have size "
+             << expected_dim_size << " but has size " << dim_size;
+    }
+  }
+  return OkStatus();
+}
+
+}  // namespace vm
+}  // namespace iree
diff --git a/iree/vm/bytecode_module.h b/iree/vm/bytecode_module.h
new file mode 100644
index 0000000..36d034e
--- /dev/null
+++ b/iree/vm/bytecode_module.h
@@ -0,0 +1,103 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_VM_BYTECODE_MODULE_H_
+#define IREE_VM_BYTECODE_MODULE_H_
+
+#include <memory>
+
+#include "iree/base/flatbuffer_util.h"
+#include "iree/rt/function.h"
+#include "iree/rt/module.h"
+#include "iree/schemas/executable_table_def_generated.h"
+#include "iree/schemas/function_table_def_generated.h"
+#include "iree/schemas/module_def_generated.h"
+#include "iree/vm/opcode_info.h"
+#include "iree/vm/source_map_resolver.h"
+
+namespace iree {
+namespace vm {
+
+using ModuleFile = FlatBufferFile<ModuleDef>;
+
+// A loaded bytecode module backed by a FlatBuffer.
+class BytecodeModule : public rt::Module {
+ public:
+  static Status ValidateStructure(const ModuleDef& module_def);
+
+  ~BytecodeModule() override;
+
+  const ModuleDef& def() const { return module_def_; }
+  const FunctionTableDef& function_table_def() const {
+    return *module_def_.function_table();
+  }
+  const ExecutableTableDef& executable_table_def() const {
+    return *module_def_.executable_table();
+  }
+
+  absl::string_view name() const override {
+    return WrapString(module_def_.name());
+  }
+
+  const rt::ModuleSignature signature() const override;
+
+  rt::SourceResolver* source_resolver() const override {
+    return &source_resolver_;
+  }
+
+  rt::Disassembler* disassembler() const override {
+    return disassembler_.get();
+  }
+
+  std::string DebugStringShort() const override;
+
+  StatusOr<const rt::Function> LookupFunctionByOrdinal(
+      rt::Function::Linkage linkage, int32_t ordinal) const override;
+
+  StatusOr<const rt::Function> LookupFunctionByName(
+      rt::Function::Linkage linkage, absl::string_view name) const override;
+
+  StatusOr<absl::string_view> GetFunctionName(rt::Function::Linkage linkage,
+                                              int32_t ordinal) const override;
+
+  StatusOr<const rt::FunctionSignature> GetFunctionSignature(
+      rt::Function::Linkage linkage, int32_t ordinal) const override;
+
+  StatusOr<const FunctionDef*> GetFunctionDef(rt::Function::Linkage linkage,
+                                              int32_t ordinal) const;
+
+  StatusOr<const MultiArchExecutableDef*> LookupMultiArchExecutable(
+      int executable_ordinal) const;
+
+ protected:
+  BytecodeModule(std::unique_ptr<ModuleFile> module_file,
+                 OpcodeTable opcode_table);
+
+  static Status ValidateArgType(const hal::BufferView& arg,
+                                const MemRefTypeDef& expected_type);
+
+ private:
+  StatusOr<int32_t> MapFunctionOrdinal(rt::Function::Linkage linkage,
+                                       int32_t ordinal) const;
+
+  std::unique_ptr<ModuleFile> module_file_;
+  const ModuleDef& module_def_;
+  mutable SourceMapResolver source_resolver_;
+  mutable std::unique_ptr<rt::Disassembler> disassembler_;
+};
+
+}  // namespace vm
+}  // namespace iree
+
+#endif  // IREE_VM_BYTECODE_MODULE_H_
diff --git a/iree/vm/bytecode_printer.h b/iree/vm/bytecode_printer.h
deleted file mode 100644
index 2f2cd5b..0000000
--- a/iree/vm/bytecode_printer.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_BYTECODE_PRINTER_H_
-#define IREE_VM_BYTECODE_PRINTER_H_
-
-#include <ostream>
-
-#include "iree/base/status.h"
-#include "iree/schemas/bytecode_def_generated.h"
-#include "iree/schemas/source_map_def_generated.h"
-#include "iree/vm/executable_table.h"
-#include "iree/vm/function_table.h"
-#include "iree/vm/opcode_info.h"
-#include "iree/vm/source_map.h"
-
-namespace iree {
-namespace vm {
-
-// Prints bytecode in a text format to enable human-inspection.
-// Optionally can interleave original source location information if a SourceMap
-// is available.
-class BytecodePrinter {
- public:
-  static std::string ToString(OpcodeTable opcode_table,
-                              const FunctionTable& function_table,
-                              const ExecutableTable& executable_table,
-                              const SourceMapResolver& source_map_resolver,
-                              const BytecodeDef& bytecode_def);
-
-  explicit BytecodePrinter(OpcodeTable opcode_table,
-                           const FunctionTable& function_table,
-                           const ExecutableTable& executable_table,
-                           const SourceMapResolver& source_map_resolver)
-      : opcode_table_(opcode_table),
-        function_table_(function_table),
-        executable_table_(executable_table),
-        source_map_resolver_(source_map_resolver) {}
-
-  StatusOr<std::string> Print(const BytecodeDef& bytecode_def) const;
-
-  Status PrintToStream(const BytecodeDef& bytecode_def,
-                       std::ostream* stream) const;
-  Status PrintToStream(absl::Span<const uint8_t> data,
-                       std::ostream* stream) const;
-
- private:
-  OpcodeTable opcode_table_;
-  const FunctionTable& function_table_;
-  const ExecutableTable& executable_table_;
-  const SourceMapResolver& source_map_resolver_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_BYTECODE_PRINTER_H_
diff --git a/iree/vm/bytecode_reader.cc b/iree/vm/bytecode_reader.cc
index cd1e0bf..65e69f3 100644
--- a/iree/vm/bytecode_reader.cc
+++ b/iree/vm/bytecode_reader.cc
@@ -17,12 +17,16 @@
 #include "iree/base/shape.h"
 #include "iree/base/status.h"
 #include "iree/hal/heap_buffer.h"
+#include "iree/vm/bytecode_module.h"
 
 namespace iree {
 namespace vm {
 
 namespace {
+
 using ::iree::hal::BufferView;
+using ::iree::rt::StackFrame;
+
 }  // namespace
 
 StatusOr<const uint8_t*> BytecodeReader::AdvanceOffset() {
@@ -30,18 +34,10 @@
   // TODO(benvanik): make a flag and/or remove.
   DVLOG(1) << "dispatch(" << stack_frame_->function().name() << "@" << offset()
            << "): " << int(*bytecode_pc_);
-  for (int i = 0; i < locals_.size(); ++i) {
-    DVLOG(1) << "local[" << i << "] " << locals_[i].DebugStringShort();
+  for (int i = 0; i < registers_->buffer_views.size(); ++i) {
+    DVLOG(1) << "local[" << i << "] "
+             << registers_->buffer_views[i].DebugStringShort();
   }
-
-  if (breakpoint_table_) {
-    auto it = breakpoint_table_->find(offset());
-    if (it != breakpoint_table_->end()) {
-      // Breakpoint hit!
-      RETURN_IF_ERROR(it->second(*stack_));
-    }
-  }
-
   return bytecode_pc_++;
 }
 
@@ -128,29 +124,26 @@
 
   // Setup state pointers for faster dereferencing.
   const auto& function = new_stack_frame->function();
-  const auto& bytecode = *function.def().bytecode();
+  ASSIGN_OR_RETURN(
+      const auto* function_def,
+      static_cast<const BytecodeModule*>(function.module())
+          ->GetFunctionDef(function.linkage(), function.ordinal()));
+  const auto& bytecode = *function_def->bytecode();
   bytecode_base_ = bytecode.contents()->Data();
   bytecode_limit_ = bytecode_base_ + bytecode.contents()->size();
   bytecode_pc_ = bytecode_base_ + new_stack_frame->offset();
-  locals_ = new_stack_frame->mutable_locals();
-  // TODO(benvanik): reimplement breakpoints as bytecode rewriting.
-  int function_ordinal = function.module()
-                             .function_table()
-                             .LookupFunctionOrdinal(function)
-                             .ValueOrDie();
-  breakpoint_table_ =
-      function.module().function_table().GetFunctionBreakpointTable(
-          function_ordinal);
+  registers_ = new_stack_frame->mutable_registers();
   return OkStatus();
 }
 
 Status BytecodeReader::CopyInputsAndSwitchStackFrame(
     StackFrame* src_stack_frame, StackFrame* dst_stack_frame) {
-  ASSIGN_OR_RETURN(int32_t src_count, ReadCount());
-  for (int i = 0; i < src_count; ++i) {
+  ASSIGN_OR_RETURN(size_t src_count, ReadCount());
+  auto& dst_buffer_views = dst_stack_frame->mutable_registers()->buffer_views;
+  for (int i = 0; i < std::min(src_count, dst_buffer_views.size()); ++i) {
     ASSIGN_OR_RETURN(auto* src_local,
-                     ReadLocal(src_stack_frame->mutable_locals()));
-    *dst_stack_frame->mutable_local(i) = *src_local;
+                     ReadLocal(src_stack_frame->mutable_registers()));
+    dst_buffer_views[i] = *src_local;
   }
   return SwitchStackFrame(dst_stack_frame);
 }
@@ -162,7 +155,7 @@
   absl::InlinedVector<BufferView*, 8> src_locals(src_count);
   for (int i = 0; i < src_count; ++i) {
     ASSIGN_OR_RETURN(src_locals[i],
-                     ReadLocal(src_stack_frame->mutable_locals()));
+                     ReadLocal(src_stack_frame->mutable_registers()));
   }
   RETURN_IF_ERROR(SwitchStackFrame(dst_stack_frame));
   ASSIGN_OR_RETURN(int32_t dst_count, ReadCount());
@@ -173,7 +166,7 @@
   }
   for (int i = 0; i < dst_count; ++i) {
     ASSIGN_OR_RETURN(auto* dst_local,
-                     ReadLocal(dst_stack_frame->mutable_locals()));
+                     ReadLocal(dst_stack_frame->mutable_registers()));
     *dst_local = *src_locals[i];
   }
   return OkStatus();
@@ -183,9 +176,9 @@
   ASSIGN_OR_RETURN(int32_t count, ReadCount());
   for (int i = 0; i < count; ++i) {
     ASSIGN_OR_RETURN(auto* src_local,
-                     ReadLocal(stack_frame_->mutable_locals()));
+                     ReadLocal(stack_frame_->mutable_registers()));
     ASSIGN_OR_RETURN(auto* dst_local,
-                     ReadLocal(stack_frame_->mutable_locals()));
+                     ReadLocal(stack_frame_->mutable_registers()));
     *dst_local = *src_local;
   }
   return OkStatus();
diff --git a/iree/vm/bytecode_reader.h b/iree/vm/bytecode_reader.h
index 6ba8227..f2b85fd 100644
--- a/iree/vm/bytecode_reader.h
+++ b/iree/vm/bytecode_reader.h
@@ -19,10 +19,10 @@
 #include "absl/container/inlined_vector.h"
 #include "iree/base/status.h"
 #include "iree/hal/buffer_view.h"
+#include "iree/rt/context.h"
+#include "iree/rt/stack.h"
+#include "iree/rt/stack_frame.h"
 #include "iree/schemas/bytecode/bytecode_v0.h"
-#include "iree/vm/function.h"
-#include "iree/vm/stack.h"
-#include "iree/vm/stack_frame.h"
 #include "iree/vm/type.h"
 
 namespace iree {
@@ -30,19 +30,19 @@
 
 class BytecodeReader {
  public:
-  explicit BytecodeReader(Stack* stack) : stack_(stack) {}
+  explicit BytecodeReader(rt::Stack* stack) : stack_(stack) {}
 
   int offset() const { return static_cast<int>(bytecode_pc_ - bytecode_base_); }
 
   StatusOr<const uint8_t*> AdvanceOffset();
 
-  Status SwitchStackFrame(StackFrame* new_stack_frame);
+  Status SwitchStackFrame(rt::StackFrame* new_stack_frame);
   Status BranchToOffset(int32_t offset);
 
-  Status CopyInputsAndSwitchStackFrame(StackFrame* src_stack_frame,
-                                       StackFrame* dst_stack_frame);
-  Status CopyResultsAndSwitchStackFrame(StackFrame* src_stack_frame,
-                                        StackFrame* dst_stack_frame);
+  Status CopyInputsAndSwitchStackFrame(rt::StackFrame* src_stack_frame,
+                                       rt::StackFrame* dst_stack_frame);
+  Status CopyResultsAndSwitchStackFrame(rt::StackFrame* src_stack_frame,
+                                        rt::StackFrame* dst_stack_frame);
   Status CopySlots();
 
   StatusOr<hal::BufferView> ReadConstant();
@@ -56,32 +56,33 @@
     return Type::FromTypeIndex(type_index);
   }
 
-  ABSL_ATTRIBUTE_ALWAYS_INLINE StatusOr<const Function> ReadFunction() {
+  ABSL_ATTRIBUTE_ALWAYS_INLINE StatusOr<const rt::Function> ReadFunction() {
     ASSIGN_OR_RETURN(auto value, ReadValue<uint32_t>());
     const auto& module = stack_frame_->module();
-    return module.function_table().LookupFunction(value);
+    return module.LookupFunctionByOrdinal(rt::Function::Linkage::kInternal,
+                                          value);
   }
 
-  ABSL_ATTRIBUTE_ALWAYS_INLINE StatusOr<const ImportFunction*>
+  ABSL_ATTRIBUTE_ALWAYS_INLINE StatusOr<const rt::Function>
   ReadImportFunction() {
     ASSIGN_OR_RETURN(auto value, ReadValue<uint32_t>());
     const auto& module = stack_frame_->module();
-    return module.function_table().LookupImport(value);
+    return stack_->context()->ResolveImport(&module, value);
   }
 
   ABSL_ATTRIBUTE_ALWAYS_INLINE StatusOr<hal::BufferView*> ReadLocal(
-      absl::Span<hal::BufferView> locals) {
+      rt::Registers* registers) {
     ASSIGN_OR_RETURN(auto value, ReadValue<uint16_t>());
-    if (value > locals.size()) {
+    if (value > registers->buffer_views.size()) {
       return OutOfRangeErrorBuilder(IREE_LOC)
              << "Out of bounds local access " << value << " of "
-             << locals.size();
+             << registers->buffer_views.size();
     }
-    return &locals[value];
+    return &registers->buffer_views[value];
   }
 
   ABSL_ATTRIBUTE_ALWAYS_INLINE StatusOr<hal::BufferView*> ReadLocal() {
-    return ReadLocal(locals_);
+    return ReadLocal(registers_);
   }
 
   Status SkipLocals(int count);
@@ -105,7 +106,7 @@
   template <typename T, size_t N = 8>
   ABSL_ATTRIBUTE_ALWAYS_INLINE StatusOr<absl::InlinedVector<T, N>>
   ReadSlotElements() {
-    ASSIGN_OR_RETURN(auto* local, ReadLocal(locals_));
+    ASSIGN_OR_RETURN(auto* local, ReadLocal(registers_));
     absl::InlinedVector<T, N> result(local->shape.element_count());
     if (sizeof(T) == local->element_size) {
       // Fast(ish) path: requested element size matches the actual element size.
@@ -154,13 +155,12 @@
     return value;
   }
 
-  Stack* stack_ = nullptr;
-  StackFrame* stack_frame_ = nullptr;
+  rt::Stack* stack_ = nullptr;
+  rt::StackFrame* stack_frame_ = nullptr;
   const uint8_t* bytecode_base_ = nullptr;
   const uint8_t* bytecode_limit_ = nullptr;
   const uint8_t* bytecode_pc_ = nullptr;
-  absl::Span<hal::BufferView> locals_;
-  FunctionTable::BreakpointTable* breakpoint_table_ = nullptr;
+  rt::Registers* registers_ = nullptr;
 };
 
 }  // namespace vm
diff --git a/iree/vm/bytecode_validator.cc b/iree/vm/bytecode_validator.cc
index c8a3bbd..968b193 100644
--- a/iree/vm/bytecode_validator.cc
+++ b/iree/vm/bytecode_validator.cc
@@ -18,7 +18,7 @@
 namespace vm {
 
 // static
-Status BytecodeValidator::Validate(const Context& context, const Module& module,
+Status BytecodeValidator::Validate(const BytecodeModule& module,
                                    const BytecodeDef& bytecode_def) {
   // TODO(benvanik): validate bytecode.
   return OkStatus();
diff --git a/iree/vm/bytecode_validator.h b/iree/vm/bytecode_validator.h
index bf435dc..429c754 100644
--- a/iree/vm/bytecode_validator.h
+++ b/iree/vm/bytecode_validator.h
@@ -17,8 +17,7 @@
 
 #include "iree/base/status.h"
 #include "iree/schemas/bytecode_def_generated.h"
-#include "iree/vm/context.h"
-#include "iree/vm/module.h"
+#include "iree/vm/bytecode_module.h"
 
 namespace iree {
 namespace vm {
@@ -28,7 +27,7 @@
 // be resolved with matching signatures.
 class BytecodeValidator {
  public:
-  static Status Validate(const Context& context, const Module& module,
+  static Status Validate(const BytecodeModule& module,
                          const BytecodeDef& bytecode_def);
 };
 
diff --git a/iree/vm/context.cc b/iree/vm/context.cc
deleted file mode 100644
index bae330a..0000000
--- a/iree/vm/context.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/context.h"
-
-#include "iree/base/flatbuffer_util.h"
-#include "iree/base/status.h"
-
-namespace iree {
-namespace vm {
-
-namespace {
-
-int NextUniqueId() {
-  static int next_id = 0;
-  return ++next_id;
-}
-
-}  // namespace
-
-Context::Context() : id_(NextUniqueId()) {}
-
-Context::~Context() = default;
-
-Status Context::RegisterNativeFunction(std::string name,
-                                       NativeFunction native_function) {
-  native_functions_.emplace_back(std::move(name), std::move(native_function));
-  return OkStatus();
-}
-
-Status Context::RegisterModule(std::unique_ptr<Module> module) {
-  // Attempt to link the module.
-  RETURN_IF_ERROR(module->mutable_function_table()->ResolveImports(
-      [&](const Module& importing_module,
-          const FunctionDef& import_function_def) -> StatusOr<ImportFunction> {
-        absl::string_view export_name = WrapString(import_function_def.name());
-
-        // Try to find a native function (we prefer these).
-        for (const auto& native_function : native_functions_) {
-          if (native_function.first == export_name) {
-            LOG(INFO) << "Resolved import '" << export_name
-                      << "' to native function";
-            return ImportFunction(importing_module, import_function_def,
-                                  native_function.second);
-          }
-        }
-
-        // Try to find an export in an existing module.
-        // We prefer the more recently registered modules.
-        // NOTE: slow O(n*m) search through all modules * exports.
-        for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
-          const auto& module = *it;
-          auto export_or = module->function_table().LookupExport(export_name);
-          if (export_or.ok()) {
-            LOG(INFO) << "Resolved import '" << export_name << "' to module "
-                      << module->name();
-            return ImportFunction(importing_module, import_function_def,
-                                  export_or.ValueOrDie());
-          }
-        }
-
-        return NotFoundErrorBuilder(IREE_LOC)
-               << "Import '" << export_name << "' could not be resolved";
-      }));
-
-  modules_.push_back(std::move(module));
-  return OkStatus();
-}
-
-StatusOr<const Module*> Context::LookupModule(
-    absl::string_view module_name) const {
-  return const_cast<Context*>(this)->LookupModule(module_name);
-}
-
-StatusOr<Module*> Context::LookupModule(absl::string_view module_name) {
-  for (const auto& module : modules_) {
-    if (module->name() == module_name) {
-      return module.get();
-    }
-  }
-  return NotFoundErrorBuilder(IREE_LOC)
-         << "No module with the name '" << module_name
-         << "' has been registered";
-}
-
-StatusOr<const Function> Context::LookupExport(
-    absl::string_view export_name) const {
-  for (const auto& module : modules_) {
-    auto export_or = module->function_table().LookupExport(export_name);
-    if (export_or.ok()) {
-      return export_or.ValueOrDie();
-    }
-  }
-  return NotFoundErrorBuilder(IREE_LOC)
-         << "No export with the name '" << export_name
-         << "' is present in the context";
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/context.h b/iree/vm/context.h
deleted file mode 100644
index 6c3682b..0000000
--- a/iree/vm/context.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_CONTEXT_H_
-#define IREE_VM_CONTEXT_H_
-
-#include <memory>
-#include <vector>
-
-#include "absl/strings/string_view.h"
-#include "absl/types/span.h"
-#include "iree/base/status.h"
-#include "iree/vm/function.h"
-#include "iree/vm/module.h"
-
-namespace iree {
-namespace vm {
-
-// An isolated execution context.
-// Effectively a sandbox where modules can be loaded and run with restricted
-// visibility. Each context may have its own set of imports that modules can
-// access and its own resource constraints.
-//
-// The function namespace is shared within a context, meaning that an import of
-// function 'a' from a module will resolve to an export of function 'a' from
-// another. Functions internal to a module are not resolved through the
-// namespace and may share names (or have no names at all).
-//
-// Modules have imports resolved automatically when loaded by searching existing
-// modules. This means that load order is important to ensure overrides are
-// respected. For example, target-specific modules should be loaded prior to
-// generic modules that may import functions defined there and if a function is
-// not available in the target-specific modules the fallback provided by the
-// generic module will be used.
-//
-// TODO(benvanik): evaluate if worth making thread-safe (epochs/generational).
-// Contexts are thread-compatible; const methods may be called concurrently from
-// any thread (including Invoke), however no threads must be using a shared
-// Context while new native functions or modules are registered.
-class Context {
- public:
-  Context();
-  Context(const Context&) = delete;
-  Context& operator=(const Context&) = delete;
-  Context(Context&&) = default;
-  Context& operator=(Context&&) = default;
-  virtual ~Context();
-
-  // A process-unique ID for the context.
-  int id() const { return id_; }
-
-  // TODO(benvanik): make immutable by moving to a static Create fn.
-  virtual Status RegisterNativeFunction(std::string name,
-                                        NativeFunction native_function);
-
-  virtual Status RegisterModule(std::unique_ptr<Module> module);
-
-  const std::vector<std::pair<std::string, NativeFunction>>& native_functions()
-      const {
-    return native_functions_;
-  }
-
-  const std::vector<std::unique_ptr<Module>>& modules() const {
-    return modules_;
-  }
-
-  StatusOr<const Module*> LookupModule(absl::string_view module_name) const;
-  StatusOr<Module*> LookupModule(absl::string_view module_name);
-  StatusOr<const Function> LookupExport(absl::string_view export_name) const;
-
- private:
-  int id_;
-  std::vector<std::pair<std::string, NativeFunction>> native_functions_;
-  std::vector<std::unique_ptr<Module>> modules_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_CONTEXT_H_
diff --git a/iree/vm/executable_table.cc b/iree/vm/executable_table.cc
deleted file mode 100644
index 0752fa3..0000000
--- a/iree/vm/executable_table.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/executable_table.h"
-
-#include "iree/base/flatbuffer_util.h"
-#include "iree/base/status.h"
-
-namespace iree {
-namespace vm {
-
-// static
-Status ExecutableTable::ValidateStructure(
-    const ExecutableTableDef& executable_table_def) {
-  if (!executable_table_def.multi_arch_executables()) {
-    // May have sequencer only fns. Fine to not have dispatchable executables.
-    return OkStatus();
-  }
-
-  // All fat executables need at least one device-specific executable.
-  const auto& multi_arch_executables =
-      *executable_table_def.multi_arch_executables();
-  for (int i = 0; i < multi_arch_executables.size(); ++i) {
-    const auto* multi_arch_executable = multi_arch_executables[i];
-    if (!multi_arch_executable || !multi_arch_executable->executables() ||
-        multi_arch_executable->executables()->size() == 0) {
-      return InvalidArgumentErrorBuilder(IREE_LOC)
-             << "Multi-arch executable ordinal " << i
-             << " is missing its contents";
-    }
-  }
-
-  return OkStatus();
-}
-
-ExecutableTable::ExecutableTable(const ExecutableTableDef& executable_table_def)
-    : executable_table_def_(executable_table_def) {}
-
-ExecutableTable::~ExecutableTable() = default;
-
-StatusOr<const MultiArchExecutableDef*>
-ExecutableTable::LookupMultiArchExecutable(int executable_ordinal) const {
-  if (executable_ordinal < 0 ||
-      executable_ordinal >=
-          executable_table_def_.multi_arch_executables()->size()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Invalid multi-arch executable ordinal " << executable_ordinal;
-  }
-  return executable_table_def_.multi_arch_executables()->Get(
-      executable_ordinal);
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/executable_table.h b/iree/vm/executable_table.h
deleted file mode 100644
index e26918d..0000000
--- a/iree/vm/executable_table.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_EXECUTABLE_TABLE_H_
-#define IREE_VM_EXECUTABLE_TABLE_H_
-
-#include "iree/base/status.h"
-#include "iree/schemas/executable_table_def_generated.h"
-
-namespace iree {
-namespace vm {
-
-// A table of executables present within a module.
-// Manages lookup and selection of executables based on target devices.
-//
-// Thread-safe.
-class ExecutableTable {
- public:
-  static Status ValidateStructure(
-      const ExecutableTableDef& executable_table_def);
-
-  explicit ExecutableTable(const ExecutableTableDef& executable_table_def);
-  ExecutableTable(const ExecutableTable&) = delete;
-  ExecutableTable& operator=(const ExecutableTable&) = delete;
-  ~ExecutableTable();
-
-  const ExecutableTableDef& def() const { return executable_table_def_; }
-
-  StatusOr<const MultiArchExecutableDef*> LookupMultiArchExecutable(
-      int executable_ordinal) const;
-
-  // TODO(benvanik): resolve executable by ID+format+features (ExecutableDef).
-
-  // TODO(benvanik): insert/get HAL executables (thread-safe!).
-
- private:
-  const ExecutableTableDef& executable_table_def_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_EXECUTABLE_TABLE_H_
diff --git a/iree/vm/fiber_state.cc b/iree/vm/fiber_state.cc
deleted file mode 100644
index 90d9fd4..0000000
--- a/iree/vm/fiber_state.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/fiber_state.h"
-
-#include <iterator>
-
-#include "absl/strings/str_join.h"
-#include "iree/base/status.h"
-
-namespace iree {
-namespace vm {
-
-FiberState::FiberState(std::shared_ptr<Instance> instance)
-    : instance_(std::move(instance)), id_(Instance::NextUniqueId()) {
-}
-
-FiberState::~FiberState() {
-}
-
-bool FiberState::is_suspended() {
-  // TODO(benvanik): implement.
-  return false;
-}
-
-Status FiberState::Suspend(SuspendCallback suspend_callback) {
-  DVLOG(1) << "Suspending fiber " << id();
-  return OkStatus();
-}
-
-Status FiberState::Resume() {
-  DVLOG(1) << "Resuming fiber " << id();
-  return OkStatus();
-}
-
-Status FiberState::Step(StepTarget step_target,
-                        SuspendCallback suspend_callback) {
-  return UnimplementedErrorBuilder(IREE_LOC) << "Step not yet implemented";
-}
-
-namespace {
-struct StackFrameFormatter {
-  void operator()(std::string* out, const StackFrame& stack_frame) const {
-    out->append(absl::StrCat(stack_frame.module().name(), ":",
-                             stack_frame.function().name(), "@",
-                             stack_frame.offset()));
-  }
-};
-}  // namespace
-
-std::string FiberState::DebugString() const {
-  auto frames = stack_.frames();
-  return absl::StrJoin(frames.begin(), frames.end(), "\n",
-                       StackFrameFormatter());
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/fiber_state.h b/iree/vm/fiber_state.h
deleted file mode 100644
index 5799831..0000000
--- a/iree/vm/fiber_state.h
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_FIBER_STATE_H_
-#define IREE_VM_FIBER_STATE_H_
-
-#include <functional>
-
-#include "absl/types/span.h"
-#include "iree/base/status.h"
-#include "iree/vm/instance.h"
-#include "iree/vm/stack.h"
-
-namespace iree {
-namespace vm {
-
-// Fiber call stack and state machine model.
-// Fibers may not line up with host application threads and execution may move
-// across threads.
-//
-// Fibers are thread-compatible. Certain methods, such as Suspend and Resume
-// (and others that explicitly call them) may be called from other threads,
-// however members and other methods should be assumed safe to use only from
-// the owning thread or when is_suspended returns true.
-class FiberState {
- public:
-  // Called when a fiber completes suspending (in response to a Suspend or Step
-  // request). The |suspend_status| will indicate if the suspension was
-  // successful.
-  using SuspendCallback = std::function<void(Status suspend_status)>;
-
-  struct StepTarget {
-    // TODO(benvanik): step target info (matching RPC message).
-    // module / function / offset
-    // relative to current: once, out, return, etc
-  };
-
-  explicit FiberState(std::shared_ptr<Instance> instance);
-  FiberState(const FiberState&) = delete;
-  FiberState& operator=(const FiberState&) = delete;
-  ~FiberState();
-
-  // A process-unique ID for the fiber.
-  int id() const { return id_; }
-
-  const std::shared_ptr<Instance>& instance() const { return instance_; }
-
-  // VM call stack.
-  // NOTE: only valid while suspended.
-  const Stack& stack() const { return stack_; }
-  Stack* mutable_stack() { return &stack_; }
-
-  // Returns true if the fiber is suspended.
-  // This only returns true if the fiber has been requested to suspend with
-  // Suspend and the runtime has acked the suspend. Once suspended (and until
-  // resumed) fiber state will not change and may be observed from any thread.
-  //
-  // Safe to call from any thread.
-  bool is_suspended();
-
-  // Suspends the fiber at the next possible chance.
-  //
-  // Fibers have a suspension depth and each call to Suspend must be matched
-  // with a call to Resume. Fibers will only resume excution when all prior
-  // Suspend calls have their matching Resume called.
-  //
-  // Optionally callers may provide a |suspend_callback| that will be called
-  // from a random thread when the fiber is suspended (or fails to suspend).
-  //
-  // Safe to call from any thread.
-  Status Suspend(SuspendCallback suspend_callback = nullptr);
-
-  // Resumes the fiber if it is suspended (or cancels a pending suspend).
-  // This may wake threads if they are currently waiting on the fiber to
-  // execute.
-  //
-  // Safe to call from any thread.
-  Status Resume();
-
-  // Steps fiber execution.
-  // This will attempt to resume the fiber and will complete asynchronously.
-  // Upon returning the fiber should be assumed resumed and callers must query
-  // is_suspended to wait until the fiber suspends again. Optionally callers may
-  // provide a |suspend_callback| that will be called from a random thread when
-  // the fiber is suspended (or fails to suspend).
-  //
-  // Safe to call from any thread while the fiber is suspended.
-  Status Step(StepTarget step_target,
-              SuspendCallback suspend_callback = nullptr);
-
-  std::string DebugString() const;
-
- private:
-  std::shared_ptr<Instance> instance_;
-  int id_;
-  Stack stack_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_FIBER_STATE_H_
diff --git a/iree/vm/function.cc b/iree/vm/function.cc
deleted file mode 100644
index 1e92c28..0000000
--- a/iree/vm/function.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/function.h"
-
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_join.h"
-#include "iree/base/flatbuffer_util.h"
-#include "iree/schemas/type_def_generated.h"
-
-namespace iree {
-namespace vm {
-
-namespace {
-
-struct TypeFormatter {
-  void operator()(std::string* out, const TypeDef* type_def) const {
-    switch (type_def->type_union_type()) {
-      case TypeDefUnion::MemRefTypeDef:
-        (*this)(out, type_def->type_union_as_MemRefTypeDef());
-        return;
-      case TypeDefUnion::DeviceTypeDef:
-        out->append("device");
-        return;
-      case TypeDefUnion::CommandBufferTypeDef:
-        out->append("command_buffer");
-        return;
-      case TypeDefUnion::EventTypeDef:
-        out->append("event");
-        return;
-      case TypeDefUnion::SemaphoreTypeDef:
-        out->append("semaphore");
-        return;
-      case TypeDefUnion::FenceTypeDef:
-        out->append("fence");
-        return;
-      default:
-        out->append("<invalid>");
-        return;
-    }
-  }
-
-  void operator()(std::string* out,
-                  const MemRefTypeDef* mem_ref_type_def) const {
-    out->append("memref<");
-    if (mem_ref_type_def->shape()) {
-      for (int dim : *mem_ref_type_def->shape()) {
-        out->append(std::to_string(dim));
-        out->append("x");
-      }
-    } else {
-      out->append("?x");
-    }
-    (*this)(out, mem_ref_type_def->element_type());
-    out->append(">");
-  }
-
-  void operator()(std::string* out, const ElementTypeDef* type_def) const {
-    switch (type_def->type_union_type()) {
-      case ElementTypeDefUnion::FloatTypeDef: {
-        const auto* float_type_def = type_def->type_union_as_FloatTypeDef();
-        out->append("f");
-        out->append(std::to_string(float_type_def->width()));
-        break;
-      }
-      case ElementTypeDefUnion::IntegerTypeDef: {
-        const auto* int_type_def = type_def->type_union_as_IntegerTypeDef();
-        out->append("i");
-        out->append(std::to_string(int_type_def->width()));
-        break;
-      }
-      case ElementTypeDefUnion::UnknownTypeDef: {
-        const auto* unknown_type_def = type_def->type_union_as_UnknownTypeDef();
-        out->append("unknown<");
-        auto dialect_str = WrapString(unknown_type_def->dialect());
-        out->append(dialect_str.data(), dialect_str.size());
-        auto type_data_str = WrapString(unknown_type_def->type_data());
-        out->append(type_data_str.data(), type_data_str.size());
-        out->append(">");
-        break;
-      }
-      default:
-        out->append("<invalid>");
-        return;
-    }
-  }
-};
-
-}  // namespace
-
-std::string Function::DebugStringShort() const {
-  return absl::StrCat(
-      name(), "(",
-      type_def().inputs()
-          ? absl::StrJoin(*type_def().inputs(), ", ", TypeFormatter())
-          : "",
-      ") -> (",
-      type_def().results()
-          ? absl::StrJoin(*type_def().results(), ", ", TypeFormatter())
-          : "",
-      ")");
-}
-
-std::string ImportFunction::DebugStringShort() const {
-  // TODO(benvanik): import function strings.
-  return "(IMPORT)";
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/function.h b/iree/vm/function.h
deleted file mode 100644
index 7f590a0..0000000
--- a/iree/vm/function.h
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_FUNCTION_H_
-#define IREE_VM_FUNCTION_H_
-
-#include <functional>
-
-#include "absl/strings/string_view.h"
-#include "absl/types/span.h"
-#include "iree/base/flatbuffer_util.h"
-#include "iree/base/status.h"
-#include "iree/hal/buffer_view.h"
-#include "iree/schemas/function_def_generated.h"
-#include "iree/schemas/type_def_generated.h"
-
-namespace iree {
-namespace vm {
-
-class Stack;
-class Module;
-
-// TODO(benvanik): reorganize this; I don't like it. maybe ImportFunction
-// shouldn't derive from Function at all?
-
-// A function defined within a Module.
-// Imported functions may be of the ImportFunction type and contain additional
-// runtime linkage information.
-class Function {
- public:
-  Function() = default;
-  Function(const Module& module, const FunctionDef& function_def)
-      : module_(&module), function_def_(&function_def) {}
-
-  absl::string_view name() const { return WrapString(function_def_->name()); }
-
-  const Module& module() const { return *module_; }
-  const FunctionDef& def() const { return *function_def_; }
-  const FunctionTypeDef& type_def() const { return *def().type(); }
-
-  int input_count() const {
-    return type_def().inputs() ? type_def().inputs()->size() : 0;
-  }
-  int result_count() const {
-    return type_def().results() ? type_def().results()->size() : 0;
-  }
-
-  std::string DebugStringShort() const;
-
- private:
-  const Module* module_ = nullptr;
-  const FunctionDef* function_def_ = nullptr;
-};
-
-inline std::ostream& operator<<(std::ostream& stream,
-                                const Function& function) {
-  stream << function.DebugStringShort();
-  return stream;
-}
-
-// TODO(benvanik): make an interface as well.
-// TODO(benvanik): pass through additional attributes.
-using NativeFunction =
-    std::function<Status(Stack* stack, absl::Span<hal::BufferView> args,
-                         absl::Span<hal::BufferView> results)>;
-
-// A function imported into a Module from either a native function or other
-// module.
-class ImportFunction : public Function {
- public:
-  enum class LinkType {
-    kNativeFunction,
-    kModule,
-  };
-
-  ImportFunction() = default;
-  ImportFunction(const Module& module, const FunctionDef& function_def,
-                 NativeFunction native_function)
-      : Function(module, function_def),
-        link_type_(LinkType::kNativeFunction),
-        native_function_(std::move(native_function)) {}
-  ImportFunction(const Module& module, const FunctionDef& function_def,
-                 Function linked_function)
-      : Function(module, function_def),
-        link_type_(LinkType::kModule),
-        linked_function_(std::move(linked_function)) {}
-  ImportFunction(const ImportFunction&) = delete;
-  ImportFunction& operator=(const ImportFunction&) = delete;
-  ImportFunction(ImportFunction&&) = default;
-  ImportFunction& operator=(ImportFunction&&) = default;
-
-  LinkType link_type() const { return link_type_; }
-  const NativeFunction& native_function() const { return native_function_; }
-  const Function& linked_function() const { return linked_function_; }
-
-  std::string DebugStringShort() const;
-
- private:
-  LinkType link_type_;
-  NativeFunction native_function_;
-  Function linked_function_;
-};
-
-inline std::ostream& operator<<(std::ostream& stream,
-                                const ImportFunction& function) {
-  stream << function.DebugStringShort();
-  return stream;
-}
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_FUNCTION_H_
diff --git a/iree/vm/function_table.cc b/iree/vm/function_table.cc
deleted file mode 100644
index 1e629bd..0000000
--- a/iree/vm/function_table.cc
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/function_table.h"
-
-#include "absl/container/flat_hash_map.h"
-#include "iree/base/flatbuffer_util.h"
-#include "iree/base/status.h"
-
-namespace iree {
-namespace vm {
-
-namespace {
-
-Status ValidateType(const FunctionTypeDef& type_def) {
-  // Ensure all fields are populated.
-  return OkStatus();
-}
-
-}  // namespace
-
-// static
-Status FunctionTable::ValidateStructure(
-    const FunctionTableDef& function_table_def) {
-  if (!function_table_def.functions()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Function table is missing the function listing";
-  }
-
-  // All functions must contain a valid type.
-  const auto& functions = *function_table_def.functions();
-  for (int i = 0; i < functions.size(); ++i) {
-    const auto* function = functions[i];
-    if (!function) {
-      return InvalidArgumentErrorBuilder(IREE_LOC)
-             << "Function ordinal " << i << " is missing its contents";
-    }
-    if (!function->type()) {
-      return InvalidArgumentErrorBuilder(IREE_LOC)
-             << "Function ordinal " << i << " is missing its type";
-    }
-    RETURN_IF_ERROR(ValidateType(*function->type()));
-  }
-
-  // Imports must also have a name (that we can use to resolve it).
-  if (function_table_def.imports()) {
-    const auto& imports = *function_table_def.imports();
-    for (int i = 0; i < imports.size(); ++i) {
-      int function_index = imports[i];
-      if (!functions[function_index]->name()) {
-        return InvalidArgumentErrorBuilder(IREE_LOC)
-               << "Import ordinal " << i << " is missing its contents";
-      }
-    }
-  }
-
-  // Exports must also have a name (that others will use to look it up).
-  if (function_table_def.exports()) {
-    const auto& exports = *function_table_def.exports();
-    for (int i = 0; i < exports.size(); ++i) {
-      int function_index = exports[i];
-      if (!functions[function_index]->name()) {
-        return InvalidArgumentErrorBuilder(IREE_LOC)
-               << "Export ordinal " << i << " is missing its contents";
-      }
-    }
-  }
-
-  return OkStatus();
-}
-
-FunctionTable::FunctionTable(const Module& module,
-                             const FunctionTableDef& function_table_def)
-    : module_(module), function_table_def_(function_table_def) {}
-
-FunctionTable::~FunctionTable() = default;
-
-Status FunctionTable::ResolveImports(ImportResolver import_resolver) {
-  if (!function_table_def_.imports()) {
-    // No imports to resolve.
-    return OkStatus();
-  }
-
-  const auto& imports = *function_table_def_.imports();
-  const auto& functions = *function_table_def_.functions();
-  for (int i = 0; i < imports.size(); ++i) {
-    const auto* function_def = functions[imports[i]];
-    ASSIGN_OR_RETURN(auto import_function,
-                     import_resolver(module_, *function_def));
-    import_functions_.push_back(std::move(import_function));
-  }
-
-  return OkStatus();
-}
-
-StatusOr<int> FunctionTable::LookupImportOrdinal(
-    absl::string_view import_name) const {
-  if (function_table_def_.imports()) {
-    const auto& imports = *function_table_def_.imports();
-    const auto& functions = *function_table_def_.functions();
-    for (int i = 0; i < imports.size(); ++i) {
-      if (WrapString(functions[imports[i]]->name()) == import_name) {
-        return i;
-      }
-    }
-  }
-  return NotFoundErrorBuilder(IREE_LOC)
-         << "Import with the name '" << import_name << "' not found in module";
-}
-
-StatusOr<const ImportFunction*> FunctionTable::LookupImport(
-    absl::string_view import_name) const {
-  ASSIGN_OR_RETURN(int import_ordinal, LookupImportOrdinal(import_name));
-  return LookupImport(import_ordinal);
-}
-
-StatusOr<const ImportFunction*> FunctionTable::LookupImport(
-    int import_ordinal) const {
-  if (import_ordinal < 0 || import_ordinal >= import_functions_.size()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Import ordinal " << import_ordinal
-           << " is outside the valid range [0, " << import_functions_.size()
-           << ")";
-  }
-  return {&import_functions_[import_ordinal]};
-}
-
-StatusOr<int> FunctionTable::LookupExportFunctionOrdinal(
-    absl::string_view export_name) const {
-  // NOTE: this is a linear scan of the export table, but since export count
-  // is usually small and the only time this lookup should happen is on module
-  // load it's (probably) fine.
-  if (function_table_def_.exports()) {
-    const auto& exports = *function_table_def_.exports();
-    for (int i = 0; i < exports.size(); ++i) {
-      int export_ordinal = exports.Get(i);
-      const auto& function_def =
-          *function_table_def_.functions()->Get(export_ordinal);
-      if (WrapString(function_def.name()) == export_name) {
-        return export_ordinal;
-      }
-    }
-  }
-  return NotFoundErrorBuilder(IREE_LOC)
-         << "Export with the name '" << export_name << "' not found in module";
-}
-
-StatusOr<const Function> FunctionTable::LookupExport(
-    absl::string_view export_name) const {
-  ASSIGN_OR_RETURN(int export_ordinal,
-                   LookupExportFunctionOrdinal(export_name));
-  return LookupFunction(export_ordinal);
-}
-
-StatusOr<const Function> FunctionTable::LookupExport(int export_ordinal) const {
-  if (!function_table_def_.exports() || export_ordinal < 0 ||
-      export_ordinal >= function_table_def_.exports()->size()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Export ordinal " << export_ordinal
-           << " is outside the valid range [0, "
-           << function_table_def_.exports()->size() << ")";
-  }
-  const auto& exports = *function_table_def_.exports();
-  int function_ordinal = exports.Get(export_ordinal);
-  return LookupFunction(function_ordinal);
-}
-
-StatusOr<const Function> FunctionTable::LookupFunction(int ordinal) const {
-  if (ordinal < 0 || ordinal >= function_table_def_.functions()->size()) {
-    return OutOfRangeErrorBuilder(IREE_LOC)
-           << "Function ordinal " << ordinal
-           << " is outside the valid range [0, "
-           << function_table_def_.functions()->size() << ")";
-  }
-  const auto* function_def = function_table_def_.functions()->Get(ordinal);
-  return Function(module_, *function_def);
-}
-
-StatusOr<int> FunctionTable::LookupFunctionOrdinal(
-    const Function& function) const {
-  const auto& functions = *function_table_def_.functions();
-  for (int i = 0; i < functions.size(); ++i) {
-    if (&function.def() == functions.Get(i)) {
-      return i;
-    }
-  }
-  return NotFoundErrorBuilder(IREE_LOC) << "Function not a member of module";
-}
-
-StatusOr<int> FunctionTable::LookupFunctionOrdinalByName(
-    absl::string_view name) const {
-  for (int i = 0; i < function_table_def_.functions()->size(); ++i) {
-    const auto* function_def = function_table_def_.functions()->Get(i);
-    if (WrapString(function_def->name()) == name) {
-      return i;
-    }
-  }
-  return NotFoundErrorBuilder(IREE_LOC)
-         << "Function '" << name
-         << "' not found in function table (or names have been stripped)";
-}
-
-Status FunctionTable::RegisterBreakpoint(int function_ordinal, int offset,
-                                         BreakpointCallback callback) {
-  if (breakpoint_tables_.empty()) {
-    breakpoint_tables_.resize(function_table_def_.functions()->size());
-  }
-  if (function_ordinal < 0 || function_ordinal > breakpoint_tables_.size()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Function ordinal " << function_ordinal << " out of bounds";
-  }
-  if (!breakpoint_tables_[function_ordinal]) {
-    breakpoint_tables_[function_ordinal] =
-        absl::make_unique<absl::flat_hash_map<int, BreakpointCallback>>();
-  }
-  auto& function_table = *breakpoint_tables_[function_ordinal];
-  function_table[offset] = std::move(callback);
-  return OkStatus();
-}
-
-Status FunctionTable::UnregisterBreakpoint(int function_ordinal, int offset) {
-  if (function_ordinal < 0 || function_ordinal > breakpoint_tables_.size()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Function ordinal " << function_ordinal << " out of bounds";
-  }
-  auto* function_table = breakpoint_tables_[function_ordinal].get();
-  if (function_table) {
-    auto it = function_table->find(offset);
-    if (it != function_table->end()) {
-      function_table->erase(it);
-    }
-  }
-  return OkStatus();
-}
-
-Status FunctionTable::UnregisterAllBreakpoints() {
-  breakpoint_tables_.clear();
-  return OkStatus();
-}
-
-FunctionTable::BreakpointTable* FunctionTable::GetFunctionBreakpointTable(
-    int function_ordinal) const {
-  if (function_ordinal < 0 || function_ordinal >= breakpoint_tables_.size()) {
-    return nullptr;
-  }
-  return breakpoint_tables_[function_ordinal].get();
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/function_table.h b/iree/vm/function_table.h
deleted file mode 100644
index e7a1034..0000000
--- a/iree/vm/function_table.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_FUNCTION_TABLE_H_
-#define IREE_VM_FUNCTION_TABLE_H_
-
-#include <functional>
-#include <vector>
-
-#include "absl/container/flat_hash_map.h"
-#include "absl/strings/string_view.h"
-#include "absl/types/span.h"
-#include "iree/base/status.h"
-#include "iree/schemas/function_table_def_generated.h"
-#include "iree/vm/function.h"
-
-namespace iree {
-namespace vm {
-
-class Stack;
-class Module;
-
-// A table of functions present within a module.
-// Manages the import table, local function resolution, and breakpoints.
-//
-// Function tables are normally thread-compatible. Debugging-specific methods
-// like RegisterBreakpoint must only be called when the debugger has suspended
-// all fibers that could be executing functions from the table.
-class FunctionTable {
- public:
-  static Status ValidateStructure(const FunctionTableDef& function_table_def);
-
-  FunctionTable(const Module& module,
-                const FunctionTableDef& function_table_def);
-  FunctionTable(const FunctionTable&) = delete;
-  FunctionTable& operator=(const FunctionTable&) = delete;
-  ~FunctionTable();
-
-  const FunctionTableDef& def() const { return function_table_def_; }
-
-  using ImportResolver = std::function<StatusOr<ImportFunction>(
-      const Module& importing_module, const FunctionDef& import_function_def)>;
-  Status ResolveImports(ImportResolver import_resolver);
-
-  StatusOr<const ImportFunction*> LookupImport(
-      absl::string_view import_name) const;
-  StatusOr<const ImportFunction*> LookupImport(int import_ordinal) const;
-
-  StatusOr<const Function> LookupExport(absl::string_view export_name) const;
-  StatusOr<const Function> LookupExport(int export_ordinal) const;
-
-  StatusOr<const Function> LookupFunction(int ordinal) const;
-
-  StatusOr<int> LookupFunctionOrdinal(const Function& function) const;
-  StatusOr<int> LookupFunctionOrdinalByName(absl::string_view name) const;
-
-  // Handles breakpoints that are encountered during execution.
-  // The current function and offset within the function will be provided.
-  // The fiber is set as suspended prior to issuing the callback and resumed
-  // if the callback returns ok.
-  //
-  // Implementations can use the return status to indicate intended program
-  // flow:
-  //  - return ok to resume the fiber and continue execution
-  //  - return abort to terminate the fiber
-  //  - return an error to propagate via normal error handling logic
-  using BreakpointCallback = std::function<Status(const Stack& stack)>;
-
-  // Registers a breakpoint for an operation offset within a function.
-  // The provided callback will be issued when the breakpoint is hit. If a
-  // breakpoint already exists for the given offset it will be replaced.
-  //
-  // The global debug lock must be held and all fibers must be suspended.
-  Status RegisterBreakpoint(int function_ordinal, int offset,
-                            BreakpointCallback callback);
-
-  // Unregisters a breakpoint, if one has been registered.
-  //
-  // The global debug lock must be held and all fibers must be suspended.
-  Status UnregisterBreakpoint(int function_ordinal, int offset);
-
-  // Unregisters all breakpoints in the function table.
-  //
-  // The global debug lock must be held and all fibers must be suspended.
-  Status UnregisterAllBreakpoints();
-
-  using BreakpointTable = absl::flat_hash_map<int, BreakpointCallback>;
-
-  // Returns the breakpoint table mapping offset to breakpoint callback.
-  // Returns nullptr if the given function does not have a breakpoint table.
-  //
-  // This table is not synchronized and while the debug lock is held it must not
-  // be accessed by any other threads. Reading is otherwise safe.
-  BreakpointTable* GetFunctionBreakpointTable(int function_ordinal) const;
-
- private:
-  StatusOr<int> LookupImportOrdinal(absl::string_view import_name) const;
-  StatusOr<int> LookupExportFunctionOrdinal(
-      absl::string_view export_name) const;
-
-  const Module& module_;
-  const FunctionTableDef& function_table_def_;
-  std::vector<ImportFunction> import_functions_;
-
-  // One slot per function in the function table. The hash map contains the
-  // breakpoints for that particular function mapped by offset within the
-  // function.
-  std::vector<std::unique_ptr<BreakpointTable>> breakpoint_tables_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_FUNCTION_TABLE_H_
diff --git a/iree/vm/instance.cc b/iree/vm/instance.cc
deleted file mode 100644
index 7668d73..0000000
--- a/iree/vm/instance.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/instance.h"
-
-#include "absl/memory/memory.h"
-#include "iree/base/source_location.h"
-#include "iree/base/status.h"
-
-namespace iree {
-namespace vm {
-
-// static
-int Instance::NextUniqueId() {
-  static int next_id = 0;
-  return ++next_id;
-}
-
-Instance::Instance()
-    : device_manager_(absl::make_unique<hal::DeviceManager>()) {}
-
-Instance::~Instance() = default;
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/instance.h b/iree/vm/instance.h
deleted file mode 100644
index 68c4524..0000000
--- a/iree/vm/instance.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_INSTANCE_H_
-#define IREE_VM_INSTANCE_H_
-
-#include <memory>
-
-#include "iree/hal/device_manager.h"
-
-namespace iree {
-namespace vm {
-
-// Shared runtime instance responsible for routing Context events, enumerating
-// and creating hardware device interfaces, and managing thread pools.
-//
-// A single runtime instance can service multiple contexts and hosting
-// applications should try to reuse a runtime as much as possible. This ensures
-// that resource allocation across contexts is handled and extraneous device
-// interaction is avoided.
-class Instance {
- public:
-  // Allocates a global unique ID.
-  static int NextUniqueId();
-
-  Instance();
-  ~Instance();
-  Instance(const Instance&) = delete;
-  Instance& operator=(const Instance&) = delete;
-
-  hal::DeviceManager* device_manager() const { return device_manager_.get(); }
-
- private:
-  std::unique_ptr<hal::DeviceManager> device_manager_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_INSTANCE_H_
diff --git a/iree/vm/module.cc b/iree/vm/module.cc
deleted file mode 100644
index 4aaa634..0000000
--- a/iree/vm/module.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/module.h"
-
-#include "absl/memory/memory.h"
-#include "iree/base/status.h"
-
-namespace iree {
-namespace vm {
-
-// static
-Status Module::ValidateStructure(const ModuleDef& module_def) {
-  // Must have a function table.
-  if (module_def.function_table()) {
-    RETURN_IF_ERROR(
-        FunctionTable::ValidateStructure(*module_def.function_table()));
-  } else {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "ModuleDef is missing a function table";
-  }
-
-  // May optionally have an executable table.
-  if (module_def.executable_table()) {
-    RETURN_IF_ERROR(
-        ExecutableTable::ValidateStructure(*module_def.executable_table()));
-  }
-
-  return OkStatus();
-}
-
-// static
-StatusOr<std::unique_ptr<Module>> Module::FromDef(const ModuleDef& module_def) {
-  ASSIGN_OR_RETURN(auto module_file, ModuleFile::Create(&module_def, []() {}));
-  return FromFile(std::move(module_file));
-}
-
-// static
-StatusOr<std::unique_ptr<Module>> Module::FromFile(
-    std::unique_ptr<ModuleFile> module_file) {
-  if (module_file->root() == nullptr) {
-    return InvalidArgumentErrorBuilder(IREE_LOC) << "No root ModuleDef present";
-  }
-  const auto& module_def = *module_file->root();
-
-  // Validates the structure of the module (but not bytecode).
-  // This ensures we don't have flatbuffer vectors will null entries, etc.
-  RETURN_IF_ERROR(Module::ValidateStructure(module_def));
-
-  auto module = absl::WrapUnique(new Module(std::move(module_file)));
-
-  // TODO(benvanik): validate internals here? or make explicit?
-
-  return {std::move(module)};
-}
-
-Module::Module(std::unique_ptr<ModuleFile> module_file)
-    : module_file_(std::move(module_file)),
-      module_def_(*module_file_->root()),
-      function_table_(*this, *module_def_.function_table()),
-      executable_table_(*module_def_.executable_table()) {}
-
-Module::~Module() = default;
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/module.h b/iree/vm/module.h
deleted file mode 100644
index 01cfc0d..0000000
--- a/iree/vm/module.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_MODULE_H_
-#define IREE_VM_MODULE_H_
-
-#include <memory>
-
-#include "iree/base/flatbuffer_util.h"
-#include "iree/schemas/module_def_generated.h"
-#include "iree/vm/executable_table.h"
-#include "iree/vm/function_table.h"
-
-namespace iree {
-namespace vm {
-
-using ModuleFile = FlatBufferFile<ModuleDef>;
-
-// A loaded bytecode module.
-class Module {
- public:
-  static Status ValidateStructure(const ModuleDef& module_def);
-
-  static StatusOr<std::unique_ptr<Module>> FromDef(const ModuleDef& module_def);
-  static StatusOr<std::unique_ptr<Module>> FromFile(
-      std::unique_ptr<ModuleFile> module_file);
-
-  Module(const Module&) = delete;
-  Module& operator=(const Module&) = delete;
-  ~Module();
-
-  absl::string_view name() const { return WrapString(module_def_.name()); }
-
-  const ModuleDef& def() const { return module_def_; }
-  const FunctionTable& function_table() const { return function_table_; }
-  FunctionTable* mutable_function_table() { return &function_table_; }
-  const ExecutableTable& executable_table() const { return executable_table_; }
-  ExecutableTable* mutable_executable_table() { return &executable_table_; }
-
- private:
-  explicit Module(std::unique_ptr<ModuleFile> module_file);
-
-  std::unique_ptr<ModuleFile> module_file_;
-  const ModuleDef& module_def_;
-  FunctionTable function_table_;
-  ExecutableTable executable_table_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_MODULE_H_
diff --git a/iree/vm/module_printer.cc b/iree/vm/module_printer.cc
deleted file mode 100644
index 2304d33..0000000
--- a/iree/vm/module_printer.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/module_printer.h"
-
-#include "iree/vm/bytecode_printer.h"
-#include "iree/vm/source_map.h"
-
-namespace iree {
-namespace vm {
-
-Status PrintModuleToStream(OpcodeTable opcode_table, const Module& module,
-                           PrintModuleFlagBitfield flags,
-                           std::ostream* stream) {
-  // TODO(benvanik): custom FunctionTable Function iterator.
-  for (int i = 0; i < module.function_table().def().functions()->size(); ++i) {
-    ASSIGN_OR_RETURN(const auto& function,
-                     module.function_table().LookupFunction(i));
-    if (function.def().bytecode()) {
-      auto source_map_resolver =
-          AllBitsSet(flags, PrintModuleFlag::kIncludeSourceMapping)
-              ? SourceMapResolver::FromFunction(module.def(), i)
-              : SourceMapResolver();
-      BytecodePrinter printer(opcode_table, module.function_table(),
-                              module.executable_table(), source_map_resolver);
-      *stream << "Function " << i << ": " << function << "\n";
-      RETURN_IF_ERROR(
-          printer.PrintToStream(*function.def().bytecode(), stream));
-      *stream << "\n";
-    } else {
-      *stream << "Function " << i << ": " << function.name() << " (import)\n";
-    }
-  }
-  return OkStatus();
-}
-
-Status PrintModuleToStream(OpcodeTable opcode_table, const Module& module,
-                           std::ostream* stream) {
-  return PrintModuleToStream(opcode_table, module, PrintModuleFlag::kNone,
-                             stream);
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/sequencer_context.cc b/iree/vm/sequencer_context.cc
deleted file mode 100644
index a8bda41..0000000
--- a/iree/vm/sequencer_context.cc
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/sequencer_context.h"
-
-#include "iree/base/flatbuffer_util.h"
-#include "iree/base/status.h"
-#include "iree/hal/buffer_view.h"
-#include "iree/vm/fiber_state.h"
-#include "iree/vm/sequencer_dispatch.h"
-
-namespace iree {
-namespace vm {
-
-namespace {
-
-using ::iree::hal::BufferView;
-
-Status ValidateElementSize(int element_bit_width,
-                           const ElementTypeDef& expected_element_type) {
-  switch (expected_element_type.type_union_type()) {
-    case ElementTypeDefUnion::FloatTypeDef: {
-      auto expected_bit_width =
-          expected_element_type.type_union_as_FloatTypeDef()->width();
-      if (element_bit_width != expected_bit_width) {
-        return InvalidArgumentErrorBuilder(IREE_LOC)
-               << "Has element bit width " << element_bit_width
-               << " but expected " << expected_bit_width;
-      }
-      return OkStatus();
-    }
-    case ElementTypeDefUnion::IntegerTypeDef: {
-      auto expected_bit_width =
-          expected_element_type.type_union_as_IntegerTypeDef()->width();
-      if (element_bit_width != expected_bit_width) {
-        return InvalidArgumentErrorBuilder(IREE_LOC)
-               << "Has element bit width " << element_bit_width
-               << " but expected " << expected_bit_width;
-      }
-      return OkStatus();
-    }
-    case ElementTypeDefUnion::UnknownTypeDef:
-    case ElementTypeDefUnion::NONE: {
-    }
-  }
-  return InvalidArgumentErrorBuilder(IREE_LOC)
-         << "Defined type has unsupported element type "
-         << EnumNameElementTypeDefUnion(
-                expected_element_type.type_union_type());
-}
-
-Status ValidateArgType(const BufferView& arg,
-                       const MemRefTypeDef& expected_type) {
-  RETURN_IF_ERROR(
-      ValidateElementSize(arg.element_size * 8, *expected_type.element_type()));
-
-  auto expected_shape = expected_type.shape();
-  if (arg.shape.size() != expected_shape->size()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Argument should have rank " << expected_shape->size()
-           << " but has rank " << arg.shape.size();
-  }
-  for (int i = 0; i < expected_shape->size(); ++i) {
-    auto dim_size = arg.shape[i];
-    auto expected_dim_size = expected_shape->Get(i);
-    if (dim_size != expected_dim_size) {
-      return InvalidArgumentErrorBuilder(IREE_LOC)
-             << "Argument dimension " << i << " should have size "
-             << expected_dim_size << " but has size " << dim_size;
-    }
-  }
-  return OkStatus();
-}
-
-}  // namespace
-
-SequencerContext::SequencerContext(std::shared_ptr<Instance> instance)
-    : instance_(std::move(instance)) {}
-
-SequencerContext::~SequencerContext() = default;
-
-Status SequencerContext::RegisterNativeFunction(
-    std::string name, NativeFunction native_function) {
-  // TODO(benvanik): provide to debugger.
-  return Context::RegisterNativeFunction(std::move(name),
-                                         std::move(native_function));
-}
-
-Status SequencerContext::RegisterModule(std::unique_ptr<Module> module) {
-  RETURN_IF_ERROR(Context::RegisterModule(std::move(module)));
-  return OkStatus();
-}
-
-Status SequencerContext::Invoke(FiberState* fiber_state, Function function,
-                                absl::Span<BufferView> args,
-                                absl::Span<BufferView> results) const {
-  // Verify arg/result counts.
-  if (args.size() != function.input_count()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Function " << function.name() << " requires "
-           << function.input_count() << " inputs but " << args.size()
-           << " provided";
-  }
-  if (results.size() != function.result_count()) {
-    return InvalidArgumentErrorBuilder(IREE_LOC)
-           << "Function " << function.name() << " requires "
-           << function.result_count() << " outputs but " << results.size()
-           << " provided";
-  }
-
-  // Push stack frame for the function we are calling.
-  auto* stack = fiber_state->mutable_stack();
-  ASSIGN_OR_RETURN(auto* callee_stack_frame, stack->PushFrame(function));
-
-  // Marshal input arguments.
-  for (int i = 0; i < args.size(); ++i) {
-    auto arg = args[i];
-    auto expected_arg_type = function.type_def().inputs()->Get(i);
-    RETURN_IF_ERROR(
-        ValidateArgType(arg, *expected_arg_type->type_union_as_MemRefTypeDef()))
-        << "Function " << function.name() << " argument " << i;
-    *callee_stack_frame->mutable_local(i) = std::move(arg);
-  }
-
-  // TODO(benvanik): change to:
-  //   get command queue (any command queue)
-  //   make command buffer
-  //   record dispatch
-  //   submit
-  //   wait on fence
-  ASSIGN_OR_RETURN(auto placement,
-                   instance_->device_manager()->ResolvePlacement({}));
-  RETURN_IF_ERROR(
-      DispatchSequence(placement, stack, callee_stack_frame, results));
-
-  // Pop the callee frame to balance out the stack.
-  RETURN_IF_ERROR(stack->PopFrame());
-
-  return OkStatus();
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/sequencer_context.h b/iree/vm/sequencer_context.h
deleted file mode 100644
index dbc4bc9..0000000
--- a/iree/vm/sequencer_context.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_SEQUENCER_CONTEXT_H_
-#define IREE_VM_SEQUENCER_CONTEXT_H_
-
-#include <memory>
-#include <vector>
-
-#include "absl/strings/string_view.h"
-#include "absl/types/span.h"
-#include "iree/base/status.h"
-#include "iree/hal/buffer_view.h"
-#include "iree/vm/context.h"
-#include "iree/vm/fiber_state.h"
-#include "iree/vm/function.h"
-#include "iree/vm/instance.h"
-#include "iree/vm/module.h"
-
-namespace iree {
-namespace vm {
-
-class SequencerContext final : public Context {
- public:
-  explicit SequencerContext(std::shared_ptr<Instance> instance);
-  ~SequencerContext() override;
-
-  Status RegisterNativeFunction(std::string name,
-                                NativeFunction native_function) override;
-
-  Status RegisterModule(std::unique_ptr<Module> module) override;
-
-  // TODO(benvanik): helpers to make passing args easier
-  Status Invoke(FiberState* fiber_state, vm::Function function,
-                absl::Span<hal::BufferView> args,
-                absl::Span<hal::BufferView> results) const;
-
- private:
-  std::shared_ptr<Instance> instance_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_CONTEXT_H_
diff --git a/iree/vm/sequencer_dispatch.cc b/iree/vm/sequencer_dispatch.cc
index 90065b9..ce5a408 100644
--- a/iree/vm/sequencer_dispatch.cc
+++ b/iree/vm/sequencer_dispatch.cc
@@ -32,10 +32,10 @@
 #include "iree/hal/device.h"
 #include "iree/hal/heap_buffer.h"
 #include "iree/schemas/bytecode/sequencer_bytecode_v0.h"
+#include "iree/vm/bytecode_module.h"
 #include "iree/vm/bytecode_reader.h"
 #include "iree/vm/bytecode_tables_sequencer.h"
 #include "iree/vm/bytecode_util.h"
-#include "iree/vm/function.h"
 #include "iree/vm/opcode_info.h"
 
 namespace iree {
@@ -65,15 +65,17 @@
 }
 
 // TODO(benvanik): insert fence callbacks and wait on fence.
-Status CallNativeFunction(Stack* stack, const ImportFunction& function) {
-  auto* stack_frame = stack->current_frame();
-
+Status CallExternalFunction(rt::Stack* stack, const rt::Function& function) {
   // Marshal inputs and outputs.
-  auto args = stack_frame->mutable_locals().subspan(0, function.input_count());
-  auto results = stack_frame->mutable_locals().subspan(args.size());
-
-  const auto& fn = function.native_function();
-  return fn(stack, args, results);
+  const auto* stack_frame = stack->current_frame();
+  auto buffer_views = absl::MakeSpan(stack_frame->registers().buffer_views);
+  absl::InlinedVector<hal::BufferView, 8> arguments(
+      buffer_views.begin(),
+      buffer_views.begin() + function.signature().argument_count());
+  absl::InlinedVector<hal::BufferView, 8> results(
+      buffer_views.begin() + arguments.size(), buffer_views.end());
+  return function.module()->Execute(stack, function, std::move(arguments),
+                                    &results);
 }
 
 // Pretty prints an array, e.g. [1, 2, 3, 4]
@@ -109,8 +111,8 @@
 
 }  // namespace
 
-Status DispatchSequence(const hal::DevicePlacement& placement, Stack* stack,
-                        StackFrame* entry_stack_frame,
+Status DispatchSequence(const hal::DevicePlacement& placement, rt::Stack* stack,
+                        rt::StackFrame* entry_stack_frame,
                         absl::Span<BufferView> entry_results) {
   // Dispatch table mapping 1:1 with bytecode ops.
   // Each entry is a label within this function that can be used for computed
@@ -164,7 +166,15 @@
   DISPATCH_CORE_OPCODE(kCall, {
     auto* old_stack_frame = stack->current_frame();
     ASSIGN_OR_RETURN(const auto& target_function, reader.ReadFunction());
+    // TODO(benvanik): rework register storage interface.
+    ASSIGN_OR_RETURN(
+        const auto* function_def,
+        static_cast<const BytecodeModule*>(target_function.module())
+            ->GetFunctionDef(target_function.linkage(),
+                             target_function.ordinal()));
     ASSIGN_OR_RETURN(auto* new_stack_frame, stack->PushFrame(target_function));
+    new_stack_frame->mutable_registers()->buffer_views.resize(
+        function_def->bytecode()->local_count());
     RETURN_IF_ERROR(
         reader.CopyInputsAndSwitchStackFrame(old_stack_frame, new_stack_frame));
     DVLOG(1) << "Call; stack now: " << stack->DebugString();
@@ -172,30 +182,20 @@
 
   DISPATCH_CORE_OPCODE(kCallImport, {
     auto* old_stack_frame = stack->current_frame();
-    ASSIGN_OR_RETURN(const auto* target_function, reader.ReadImportFunction());
-    switch (target_function->link_type()) {
-      case ImportFunction::LinkType::kModule: {
-        ASSIGN_OR_RETURN(auto* new_stack_frame,
-                         stack->PushFrame(target_function->linked_function()));
-        RETURN_IF_ERROR(reader.CopyInputsAndSwitchStackFrame(old_stack_frame,
-                                                             new_stack_frame));
-        DVLOG(1) << "Call module import; stack now: " << stack->DebugString();
-        break;
-      }
-      case ImportFunction::LinkType::kNativeFunction: {
-        ASSIGN_OR_RETURN(auto* new_stack_frame,
-                         stack->PushFrame(*target_function));
-        RETURN_IF_ERROR(reader.CopyInputsAndSwitchStackFrame(old_stack_frame,
-                                                             new_stack_frame));
-        DVLOG(1) << "Call native import; stack now: " << stack->DebugString();
-        RETURN_IF_ERROR(CallNativeFunction(stack, *target_function));
-        RETURN_IF_ERROR(reader.CopyResultsAndSwitchStackFrame(old_stack_frame,
-                                                              new_stack_frame));
-        RETURN_IF_ERROR(stack->PopFrame());
-        DVLOG(1) << "Return from native; stack now: " << stack->DebugString();
-        break;
-      }
-    }
+    ASSIGN_OR_RETURN(const auto& target_function, reader.ReadImportFunction());
+    ASSIGN_OR_RETURN(auto* new_stack_frame, stack->PushFrame(target_function));
+    // TODO(benvanik): rework register storage interface.
+    const auto& signature = target_function.signature();
+    new_stack_frame->mutable_registers()->buffer_views.resize(
+        signature.argument_count() + signature.result_count());
+    RETURN_IF_ERROR(
+        reader.CopyInputsAndSwitchStackFrame(old_stack_frame, new_stack_frame));
+    DVLOG(1) << "Call native import; stack now: " << stack->DebugString();
+    RETURN_IF_ERROR(CallExternalFunction(stack, target_function));
+    RETURN_IF_ERROR(reader.CopyResultsAndSwitchStackFrame(old_stack_frame,
+                                                          new_stack_frame));
+    RETURN_IF_ERROR(stack->PopFrame());
+    DVLOG(1) << "Return from native; stack now: " << stack->DebugString();
   });
 
   DISPATCH_CORE_OPCODE(kCallIndirect, {
@@ -209,8 +209,9 @@
       // Returning from entry function. Marshal results from the return stmt.
       ASSIGN_OR_RETURN(int32_t src_count, reader.ReadCount());
       for (int i = 0; i < src_count; ++i) {
-        ASSIGN_OR_RETURN(auto* src_local,
-                         reader.ReadLocal(old_stack_frame->mutable_locals()));
+        ASSIGN_OR_RETURN(
+            auto* src_local,
+            reader.ReadLocal(old_stack_frame->mutable_registers()));
         entry_results[i] = std::move(*src_local);
       }
       DVLOG(1) << "Returning to entry";
@@ -259,11 +260,10 @@
     // TODO(benvanik): the real sequencer :)
     ASSIGN_OR_RETURN(auto dispatch_ordinal, reader.ReadInt32());
     ASSIGN_OR_RETURN(auto export_ordinal, reader.ReadUint16_t());
-    auto& executable_table =
-        stack->current_frame()->module().executable_table();
     ASSIGN_OR_RETURN(
-        auto* multi_arch_executable_def,
-        executable_table.LookupMultiArchExecutable(dispatch_ordinal));
+        const auto* multi_arch_executable_def,
+        static_cast<const BytecodeModule&>(stack->current_frame()->module())
+            .LookupMultiArchExecutable(dispatch_ordinal));
     if (export_ordinal >= multi_arch_executable_def->entry_point_count()) {
       return InvalidArgumentErrorBuilder(IREE_LOC)
              << "Invalid executable export ordinal " << export_ordinal;
diff --git a/iree/vm/sequencer_dispatch.h b/iree/vm/sequencer_dispatch.h
index fc664db..0251c17 100644
--- a/iree/vm/sequencer_dispatch.h
+++ b/iree/vm/sequencer_dispatch.h
@@ -18,15 +18,15 @@
 #include "iree/base/status.h"
 #include "iree/hal/buffer_view.h"
 #include "iree/hal/device_placement.h"
-#include "iree/vm/stack.h"
-#include "iree/vm/stack_frame.h"
+#include "iree/rt/stack.h"
+#include "iree/rt/stack_frame.h"
 
 namespace iree {
 namespace vm {
 
 // TODO(benvanik): API that supports yielding.
-Status DispatchSequence(const hal::DevicePlacement& placement, Stack* stack,
-                        StackFrame* entry_stack_frame,
+Status DispatchSequence(const hal::DevicePlacement& placement, rt::Stack* stack,
+                        rt::StackFrame* entry_stack_frame,
                         absl::Span<hal::BufferView> entry_results);
 
 }  // namespace vm
diff --git a/iree/vm/sequencer_module.cc b/iree/vm/sequencer_module.cc
new file mode 100644
index 0000000..1906ef6
--- /dev/null
+++ b/iree/vm/sequencer_module.cc
@@ -0,0 +1,112 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "iree/vm/sequencer_module.h"
+
+#include "absl/memory/memory.h"
+#include "iree/base/status.h"
+#include "iree/base/tracing.h"
+#include "iree/hal/buffer_view.h"
+#include "iree/rt/context.h"
+#include "iree/rt/instance.h"
+#include "iree/vm/bytecode_tables_sequencer.h"
+#include "iree/vm/sequencer_dispatch.h"
+
+namespace iree {
+namespace vm {
+
+namespace {
+
+using ::iree::hal::BufferView;
+using ::iree::rt::Function;
+using ::iree::rt::Module;
+
+}  // namespace
+
+// static
+StatusOr<ref_ptr<rt::Module>> SequencerModule::FromDef(
+    const ModuleDef& module_def) {
+  ASSIGN_OR_RETURN(auto module_file, ModuleFile::Create(&module_def, []() {}));
+  return FromFile(std::move(module_file));
+}
+
+// static
+StatusOr<ref_ptr<rt::Module>> SequencerModule::FromFile(
+    std::unique_ptr<ModuleFile> module_file) {
+  if (module_file->root() == nullptr) {
+    return InvalidArgumentErrorBuilder(IREE_LOC) << "No root ModuleDef present";
+  }
+  const auto& module_def = *module_file->root();
+
+  // Validates the structure of the module (but not bytecode).
+  // This ensures we don't have flatbuffer vectors will null entries, etc.
+  RETURN_IF_ERROR(BytecodeModule::ValidateStructure(module_def));
+
+  auto module = assign_ref(new SequencerModule(std::move(module_file)));
+
+  // TODO(benvanik): validate internals here? or make explicit?
+
+  return {std::move(module)};
+}
+
+SequencerModule::SequencerModule(std::unique_ptr<ModuleFile> module_file)
+    : BytecodeModule(std::move(module_file), sequencer_opcode_table()) {}
+
+SequencerModule::~SequencerModule() = default;
+
+Status SequencerModule::Execute(
+    rt::Stack* stack, const Function function,
+    absl::InlinedVector<hal::BufferView, 8> arguments,
+    absl::InlinedVector<hal::BufferView, 8>* results) const {
+  IREE_TRACE_SCOPE0("SequencerModule::Execute");
+
+  // Push stack frame for the function we are calling.
+  ASSIGN_OR_RETURN(auto* callee_stack_frame, stack->PushFrame(function));
+
+  // TODO(benvanik): rework register storage interface.
+  ASSIGN_OR_RETURN(const auto* function_def,
+                   GetFunctionDef(function.linkage(), function.ordinal()));
+  auto* registers = callee_stack_frame->mutable_registers();
+  registers->buffer_views.resize(function_def->bytecode()->local_count());
+
+  // Marshal input arguments.
+  for (int i = 0; i < arguments.size(); ++i) {
+    auto arg = arguments[i];
+    auto expected_arg_type = function_def->type()->inputs()->Get(i);
+    RETURN_IF_ERROR(BytecodeModule::ValidateArgType(
+        arg, *expected_arg_type->type_union_as_MemRefTypeDef()))
+        << "Function " << function.name() << " argument " << i;
+    registers->buffer_views[i] = std::move(arg);
+  }
+
+  // TODO(benvanik): change to:
+  //   get command queue (any command queue)
+  //   make command buffer
+  //   record dispatch
+  //   submit
+  //   wait on fence
+  ASSIGN_OR_RETURN(
+      auto placement,
+      stack->context()->instance()->device_manager()->ResolvePlacement({}));
+  RETURN_IF_ERROR(
+      DispatchSequence(placement, stack, callee_stack_frame, results));
+
+  // Pop the callee frame to balance out the stack.
+  RETURN_IF_ERROR(stack->PopFrame());
+
+  return OkStatus();
+}
+
+}  // namespace vm
+}  // namespace iree
diff --git a/iree/vm/sequencer_module.h b/iree/vm/sequencer_module.h
new file mode 100644
index 0000000..b9bb176
--- /dev/null
+++ b/iree/vm/sequencer_module.h
@@ -0,0 +1,46 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_VM_SEQUENCER_MODULE_H_
+#define IREE_VM_SEQUENCER_MODULE_H_
+
+#include <memory>
+
+#include "iree/vm/bytecode_module.h"
+
+namespace iree {
+namespace vm {
+
+// A module using the sequencer bytecode ops.
+class SequencerModule final : public BytecodeModule {
+ public:
+  static StatusOr<ref_ptr<rt::Module>> FromDef(const ModuleDef& module_def);
+  static StatusOr<ref_ptr<rt::Module>> FromFile(
+      std::unique_ptr<ModuleFile> module_file);
+
+  ~SequencerModule() override;
+
+  Status Execute(
+      rt::Stack* stack, const rt::Function function,
+      absl::InlinedVector<hal::BufferView, 8> arguments,
+      absl::InlinedVector<hal::BufferView, 8>* results) const override;
+
+ private:
+  explicit SequencerModule(std::unique_ptr<ModuleFile> module_file);
+};
+
+}  // namespace vm
+}  // namespace iree
+
+#endif  // IREE_VM_SEQUENCER_MODULE_H_
diff --git a/iree/vm/source_map.h b/iree/vm/source_map.h
deleted file mode 100644
index 32cd41e..0000000
--- a/iree/vm/source_map.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_SOURCE_MAP_H_
-#define IREE_VM_SOURCE_MAP_H_
-
-#include "absl/strings/string_view.h"
-#include "absl/types/optional.h"
-#include "iree/base/status.h"
-#include "iree/schemas/module_def_generated.h"
-#include "iree/schemas/source_map_def_generated.h"
-
-namespace iree {
-namespace vm {
-
-class SourceLocation {
- public:
-  static bool Equal(const SourceLocation& a, const SourceLocation& b);
-
-  SourceLocation() = default;
-  SourceLocation(const SourceMapDef& source_map_def,
-                 const FunctionSourceMapDef& function_source_map,
-                 int location_ordinal)
-      : source_map_def_(&source_map_def),
-        function_source_map_(&function_source_map),
-        location_ordinal_(location_ordinal) {}
-
-  std::string DebugStringShort() const;
-
-  bool empty() const { return source_map_def_ == nullptr; }
-
- private:
-  const SourceMapDef* source_map_def_ = nullptr;
-  const FunctionSourceMapDef* function_source_map_ = nullptr;
-  int location_ordinal_ = 0;
-};
-
-inline bool operator==(const SourceLocation& a, const SourceLocation& b) {
-  return SourceLocation::Equal(a, b);
-}
-
-inline bool operator!=(const SourceLocation& a, const SourceLocation& b) {
-  return !(a == b);
-}
-
-class SourceMap {
- public:
-  static SourceMap FromModule(const ModuleDef& module_def);
-
-  SourceMap() = default;
-  explicit SourceMap(const SourceMapDef& source_map_def)
-      : source_map_def_(&source_map_def) {}
-
-  bool empty() const { return source_map_def_ == nullptr; }
-  const SourceMapDef* def() const { return source_map_def_; }
-
-  StatusOr<absl::string_view> GetUniqueString(int string_index) const;
-
-  StatusOr<const FunctionSourceMapDef*> GetFunctionSourceMap(
-      int function_ordinal) const;
-
- private:
-  const SourceMapDef* source_map_def_ = nullptr;
-};
-inline std::ostream& operator<<(std::ostream& stream,
-                                const SourceLocation& location) {
-  stream << location.DebugStringShort();
-  return stream;
-}
-
-class SourceMapResolver {
- public:
-  static SourceMapResolver FromFunction(const ModuleDef& module_def,
-                                        int function_ordinal);
-
-  SourceMapResolver() = default;
-
-  absl::optional<SourceLocation> ResolveBytecodeOffset(int offset) const;
-
- private:
-  SourceMapResolver(SourceMap source_map,
-                    const FunctionSourceMapDef& function_source_map)
-      : source_map_(std::move(source_map)),
-        function_source_map_(&function_source_map) {}
-
-  SourceMap source_map_;
-  const FunctionSourceMapDef* function_source_map_ = nullptr;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_SOURCE_MAP_H_
diff --git a/iree/vm/source_map.cc b/iree/vm/source_map_resolver.cc
similarity index 72%
rename from iree/vm/source_map.cc
rename to iree/vm/source_map_resolver.cc
index 4b8467f..96025e4 100644
--- a/iree/vm/source_map.cc
+++ b/iree/vm/source_map_resolver.cc
@@ -12,23 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "iree/vm/source_map.h"
-
-#include <sstream>
+#include "iree/vm/source_map_resolver.h"
 
 #include "iree/base/flatbuffer_util.h"
 #include "iree/base/status.h"
+#include "iree/schemas/source_map_def_generated.h"
 
 namespace iree {
 namespace vm {
 
 namespace {
 
-Status PrintLocation(const SourceMap& source_map,
+Status PrintLocation(const SourceMapResolver& source_map,
                      const FunctionSourceMapDef& function_source_map,
                      const LocationDef& location, std::ostream* stream);
 
-Status PrintFileLocation(const SourceMap& source_map,
+Status PrintFileLocation(const SourceMapResolver& source_map,
                          const FunctionSourceMapDef& function_source_map,
                          const FileLocationDef& location,
                          std::ostream* stream) {
@@ -38,7 +37,7 @@
   return OkStatus();
 }
 
-Status PrintNameLocation(const SourceMap& source_map,
+Status PrintNameLocation(const SourceMapResolver& source_map,
                          const FunctionSourceMapDef& function_source_map,
                          const NameLocationDef& location,
                          std::ostream* stream) {
@@ -47,7 +46,7 @@
   return OkStatus();
 }
 
-Status PrintCallSiteLocation(const SourceMap& source_map,
+Status PrintCallSiteLocation(const SourceMapResolver& source_map,
                              const FunctionSourceMapDef& function_source_map,
                              const CallSiteLocationDef& location,
                              std::ostream* stream) {
@@ -55,7 +54,7 @@
   return OkStatus();
 }
 
-Status PrintFusedLocation(const SourceMap& source_map,
+Status PrintFusedLocation(const SourceMapResolver& source_map,
                           const FunctionSourceMapDef& function_source_map,
                           const FusedLocationDef& location,
                           std::ostream* stream) {
@@ -74,7 +73,7 @@
   return OkStatus();
 }
 
-Status PrintLocation(const SourceMap& source_map,
+Status PrintLocation(const SourceMapResolver& source_map,
                      const FunctionSourceMapDef& function_source_map,
                      const LocationDef& location, std::ostream* stream) {
   switch (location.location_union_type()) {
@@ -104,36 +103,15 @@
 }  // namespace
 
 // static
-bool SourceLocation::Equal(const SourceLocation& a, const SourceLocation& b) {
-  return a.source_map_def_ == b.source_map_def_ &&
-         a.function_source_map_ == b.function_source_map_ &&
-         a.location_ordinal_ == b.location_ordinal_;
-}
-
-std::string SourceLocation::DebugStringShort() const {
-  if (empty()) {
-    return "<unknown>";
-  }
-  std::ostringstream stream;
-  const auto& location =
-      *function_source_map_->location_table()->Get(location_ordinal_);
-  auto status = PrintLocation(SourceMap(*source_map_def_),
-                              *function_source_map_, location, &stream);
-  if (!status.ok()) {
-    stream << status;
-  }
-  return stream.str();
-}
-
-// static
-SourceMap SourceMap::FromModule(const ModuleDef& module_def) {
+SourceMapResolver SourceMapResolver::FromModule(const ModuleDef& module_def) {
   if (module_def.source_map()) {
-    return SourceMap{*module_def.source_map()};
+    return SourceMapResolver{*module_def.source_map()};
   }
   return {};
 }
 
-StatusOr<absl::string_view> SourceMap::GetUniqueString(int string_index) const {
+StatusOr<absl::string_view> SourceMapResolver::GetUniqueString(
+    int string_index) const {
   if (empty()) {
     return NotFoundErrorBuilder(IREE_LOC) << "No source map present";
   }
@@ -145,7 +123,7 @@
          << "String index " << string_index << " not present in string table";
 }
 
-StatusOr<const FunctionSourceMapDef*> SourceMap::GetFunctionSourceMap(
+StatusOr<const FunctionSourceMapDef*> SourceMapResolver::GetFunctionSourceMap(
     int function_ordinal) const {
   if (empty()) {
     return NotFoundErrorBuilder(IREE_LOC) << "No source map present";
@@ -163,28 +141,16 @@
          << " source map not present in function table";
 }
 
-// static
-SourceMapResolver SourceMapResolver::FromFunction(const ModuleDef& module_def,
-                                                  int function_ordinal) {
-  auto source_map = SourceMap::FromModule(module_def);
-  if (source_map.empty()) {
-    return {};
-  }
-  auto function_source_map_or =
-      source_map.GetFunctionSourceMap(function_ordinal);
+absl::optional<rt::SourceLocation> SourceMapResolver::ResolveFunctionOffset(
+    const rt::Function& function, rt::SourceOffset offset) {
+  if (empty()) return absl::nullopt;
+  auto function_source_map_or = GetFunctionSourceMap(function.ordinal());
   if (!function_source_map_or.ok()) {
-    return {};
+    return absl::nullopt;
   }
-  return SourceMapResolver(source_map, *function_source_map_or.ValueOrDie());
-}
-
-absl::optional<SourceLocation> SourceMapResolver::ResolveBytecodeOffset(
-    int offset) const {
-  if (!function_source_map_) {
-    return {};
-  }
-
-  const auto* bytecode_map = function_source_map_->bytecode_map();
+  const auto* function_source_map = function_source_map_or.ValueOrDie();
+  const auto* bytecode_map = function_source_map->bytecode_map();
+  if (!bytecode_map) return absl::nullopt;
 
   // TODO(benvanik): allow fuzzy offset matching/table sparsity.
   int location_ordinal = -1;
@@ -195,11 +161,33 @@
     }
   }
   if (location_ordinal == -1) {
-    return {};
+    return absl::nullopt;
   }
 
-  return SourceLocation(*source_map_.def(), *function_source_map_,
-                        location_ordinal);
+  return rt::SourceLocation(this,
+                            {
+                                reinterpret_cast<uint64_t>(function_source_map),
+                                static_cast<uint64_t>(location_ordinal),
+                            });
+}
+
+void SourceMapResolver::PrintSourceLocation(
+    rt::SourceResolverArgs resolver_args, std::ostream* stream) const {
+  if (empty()) {
+    *stream << "<unknown>";
+    return;
+  }
+
+  auto* function_source_map =
+      reinterpret_cast<FunctionSourceMapDef*>(resolver_args[0]);
+  int location_ordinal = static_cast<int>(resolver_args[1]);
+
+  const auto& location =
+      *function_source_map->location_table()->Get(location_ordinal);
+  auto status = PrintLocation(*this, *function_source_map, location, stream);
+  if (!status.ok()) {
+    *stream << status;
+  }
 }
 
 }  // namespace vm
diff --git a/iree/vm/source_map_resolver.h b/iree/vm/source_map_resolver.h
new file mode 100644
index 0000000..5c8f7c2
--- /dev/null
+++ b/iree/vm/source_map_resolver.h
@@ -0,0 +1,57 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IREE_VM_SOURCE_MAP_RESOLVER_H_
+#define IREE_VM_SOURCE_MAP_RESOLVER_H_
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "iree/base/status.h"
+#include "iree/rt/source_resolver.h"
+#include "iree/schemas/module_def_generated.h"
+#include "iree/schemas/source_map_def_generated.h"
+
+namespace iree {
+namespace vm {
+
+class SourceMapResolver final : public rt::SourceResolver {
+ public:
+  static SourceMapResolver FromModule(const ModuleDef& module_def);
+
+  SourceMapResolver() = default;
+  explicit SourceMapResolver(const SourceMapDef& source_map_def)
+      : source_map_def_(&source_map_def) {}
+
+  bool empty() const { return source_map_def_ == nullptr; }
+  const SourceMapDef* def() const { return source_map_def_; }
+
+  StatusOr<absl::string_view> GetUniqueString(int string_index) const;
+
+  StatusOr<const FunctionSourceMapDef*> GetFunctionSourceMap(
+      int function_ordinal) const;
+
+  absl::optional<rt::SourceLocation> ResolveFunctionOffset(
+      const rt::Function& function, rt::SourceOffset offset) override;
+
+  void PrintSourceLocation(rt::SourceResolverArgs resolver_args,
+                           std::ostream* stream) const override;
+
+ private:
+  const SourceMapDef* source_map_def_ = nullptr;
+};
+
+}  // namespace vm
+}  // namespace iree
+
+#endif  // IREE_VM_SOURCE_MAP_RESOLVER_H_
diff --git a/iree/vm/stack.cc b/iree/vm/stack.cc
deleted file mode 100644
index 5912173..0000000
--- a/iree/vm/stack.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/stack.h"
-
-#include <iterator>
-
-#include "absl/strings/str_join.h"
-#include "iree/base/status.h"
-
-namespace iree {
-namespace vm {
-
-constexpr int Stack::kMaxStackDepth;
-
-Stack::Stack() = default;
-
-Stack::~Stack() = default;
-
-StatusOr<StackFrame*> Stack::PushFrame(Function function) {
-  if (stack_depth_ + 1 > kMaxStackDepth) {
-    return InternalErrorBuilder(IREE_LOC)
-           << "Max stack depth of " << kMaxStackDepth << " exceeded";
-  }
-  stack_[stack_depth_++] = StackFrame(function);
-
-  // TODO(benvanik): WTF scope enter.
-
-  return current_frame();
-}
-
-StatusOr<StackFrame*> Stack::PushFrame(const ImportFunction& function) {
-  if (stack_depth_ + 1 > kMaxStackDepth) {
-    return InternalErrorBuilder(IREE_LOC)
-           << "Max stack depth of " << kMaxStackDepth << " exceeded";
-  }
-  stack_[stack_depth_++] = StackFrame(function);
-
-  // TODO(benvanik): WTF scope enter.
-
-  return current_frame();
-}
-
-Status Stack::PopFrame() {
-  if (stack_depth_ == 0) {
-    return InternalErrorBuilder(IREE_LOC) << "Unbalanced stack pop";
-  }
-
-  // TODO(benvanik): WTF scope leave.
-
-  --stack_depth_;
-  return OkStatus();
-}
-
-namespace {
-struct StackFrameFormatter {
-  void operator()(std::string* out, const StackFrame& stack_frame) const {
-    out->append(absl::StrCat(stack_frame.module().name(), ":",
-                             stack_frame.function().name(), "@",
-                             stack_frame.offset()));
-  }
-};
-}  // namespace
-
-std::string Stack::DebugString() const {
-  return absl::StrJoin(std::begin(stack_), std::begin(stack_) + stack_depth_,
-                       "\n", StackFrameFormatter());
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/stack.h b/iree/vm/stack.h
deleted file mode 100644
index 7228742..0000000
--- a/iree/vm/stack.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_STACK_H_
-#define IREE_VM_STACK_H_
-
-#include <functional>
-
-#include "absl/types/span.h"
-#include "iree/base/status.h"
-#include "iree/vm/stack_frame.h"
-
-namespace iree {
-namespace vm {
-
-// VM call stack.
-//
-// Stacks are thread-compatible.
-class Stack {
- public:
-  static constexpr int kMaxStackDepth = 32;
-
-  Stack();
-  Stack(const Stack&) = delete;
-  Stack& operator=(const Stack&) = delete;
-  ~Stack();
-
-  absl::Span<const StackFrame> frames() const {
-    return absl::MakeConstSpan(stack_, stack_depth_);
-  }
-  absl::Span<StackFrame> mutable_frames() {
-    return absl::MakeSpan(stack_, stack_depth_);
-  }
-
-  StackFrame* current_frame() {
-    return stack_depth_ > 0 ? &stack_[stack_depth_ - 1] : nullptr;
-  }
-  const StackFrame* current_frame() const {
-    return stack_depth_ > 0 ? &stack_[stack_depth_ - 1] : nullptr;
-  }
-  StackFrame* caller_frame() {
-    return stack_depth_ > 1 ? &stack_[stack_depth_ - 2] : nullptr;
-  }
-  const StackFrame* caller_frame() const {
-    return stack_depth_ > 1 ? &stack_[stack_depth_ - 2] : nullptr;
-  }
-
-  StatusOr<StackFrame*> PushFrame(Function function);
-  StatusOr<StackFrame*> PushFrame(const ImportFunction& function);
-  Status PopFrame();
-
-  std::string DebugString() const;
-
- private:
-  StackFrame stack_[kMaxStackDepth];
-  int stack_depth_ = 0;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_STACK_H_
diff --git a/iree/vm/stack_frame.cc b/iree/vm/stack_frame.cc
deleted file mode 100644
index 3974470..0000000
--- a/iree/vm/stack_frame.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "iree/vm/stack_frame.h"
-
-#include "iree/base/status.h"
-
-namespace iree {
-namespace vm {
-
-StackFrame::StackFrame(Function function) : function_(function) {
-  const auto* bytecode_def = function_.def().bytecode();
-  if (bytecode_def) {
-    offset_limit_ = bytecode_def->contents()->Length();
-    locals_.resize(bytecode_def->local_count());
-  } else {
-    locals_.resize(function_.input_count() + function_.result_count());
-  }
-}
-
-StackFrame::StackFrame(const ImportFunction& function)
-    : function_(function), import_function_(&function) {}
-
-Status StackFrame::set_offset(int offset) {
-  if (offset < 0 || offset > offset_limit_) {
-    return OutOfRangeErrorBuilder(IREE_LOC)
-           << "Offset " << offset
-           << " is outside of the bytecode body limit of " << offset_limit_;
-  }
-  offset_ = offset;
-  return OkStatus();
-}
-
-}  // namespace vm
-}  // namespace iree
diff --git a/iree/vm/stack_frame.h b/iree/vm/stack_frame.h
deleted file mode 100644
index a39c789..0000000
--- a/iree/vm/stack_frame.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_VM_STACK_FRAME_H_
-#define IREE_VM_STACK_FRAME_H_
-
-#include <vector>
-
-#include "absl/types/span.h"
-#include "iree/hal/buffer_view.h"
-#include "iree/vm/function.h"
-#include "iree/vm/module.h"
-
-namespace iree {
-namespace vm {
-
-// A single frame on the call stack containing current execution state and
-// local values.
-//
-// StackFrames are designed to be serialized so that suspend and resume is
-// possible. This means that most state is stored either entirely within the
-// frame or references to non-pointer values (such as other function indices).
-// BufferViews require special care to allow rendezvous and liveness tracking.
-class StackFrame {
- public:
-  StackFrame() = default;
-  explicit StackFrame(Function function);
-  explicit StackFrame(const ImportFunction& function);
-  StackFrame(const StackFrame&) = delete;
-  StackFrame& operator=(const StackFrame&) = delete;
-  StackFrame(StackFrame&&) = default;
-  StackFrame& operator=(StackFrame&&) = default;
-
-  const Module& module() const { return function_.module(); }
-  const Function& function() const { return function_; }
-
-  inline int offset() const { return offset_; }
-  Status set_offset(int offset);
-  inline int* mutable_offset() { return &offset_; }
-
-  inline const hal::BufferView& local(int ordinal) { return locals_[ordinal]; }
-  inline hal::BufferView* mutable_local(int ordinal) {
-    return &locals_[ordinal];
-  }
-
-  inline absl::Span<const hal::BufferView> locals() const {
-    return absl::MakeConstSpan(locals_);
-  }
-  inline absl::Span<hal::BufferView> mutable_locals() {
-    return absl::MakeSpan(locals_);
-  }
-
- private:
-  Function function_;
-  const ImportFunction* import_function_;
-  int offset_ = 0;
-  int offset_limit_ = 0;
-
-  // TODO(benvanik): replace with a placed allocation.
-  std::vector<hal::BufferView> locals_;
-};
-
-}  // namespace vm
-}  // namespace iree
-
-#endif  // IREE_VM_STACK_FRAME_H_