Rewriting/simplifying DynamicLibrary in C. (#5221)

This allowed for a lot of file IO code to go away - there was needless
abstraction here as there was only a single user of a lot of these things
that was already platform-specialized.

Progress on #4369 and #3848.
Fixes #4642.
Unblocks #3845, which can now be added cleanly.
diff --git a/iree/base/BUILD b/iree/base/BUILD
index 32e4a10..e2fc7bc 100644
--- a/iree/base/BUILD
+++ b/iree/base/BUILD
@@ -64,26 +64,6 @@
 #===------------------------------------------------------------------------===#
 
 cc_library(
-    name = "dynamic_library",
-    srcs = [
-        "dynamic_library_posix.cc",
-        "dynamic_library_win32.cc",
-    ],
-    hdrs = ["dynamic_library.h"],
-    deps = [
-        ":core_headers",
-        ":logging",
-        ":status",
-        ":tracing",
-        "//build_tools:default_linkopts",
-        "//iree/base/internal:file_path",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
-    ],
-)
-
-cc_library(
     name = "flatcc",
     hdrs = ["flatcc.h"],
     deps = [
diff --git a/iree/base/CMakeLists.txt b/iree/base/CMakeLists.txt
index 3855042..71c2c05 100644
--- a/iree/base/CMakeLists.txt
+++ b/iree/base/CMakeLists.txt
@@ -50,26 +50,6 @@
 
 iree_cc_library(
   NAME
-    dynamic_library
-  HDRS
-    "dynamic_library.h"
-  SRCS
-    "dynamic_library_posix.cc"
-    "dynamic_library_win32.cc"
-  DEPS
-    ::core_headers
-    ::logging
-    ::status
-    ::tracing
-    absl::memory
-    absl::span
-    absl::strings
-    iree::base::internal::file_path
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
     flatcc
   HDRS
     "flatcc.h"
diff --git a/iree/base/dynamic_library.h b/iree/base/dynamic_library.h
deleted file mode 100644
index 213bb3d..0000000
--- a/iree/base/dynamic_library.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef IREE_BASE_DYNAMIC_LIBRARY_H_
-#define IREE_BASE_DYNAMIC_LIBRARY_H_
-
-#include <memory>
-#include <string>
-
-#include "absl/types/span.h"
-#include "iree/base/status.h"
-
-namespace iree {
-
-// Dynamic library / shared object cross-platform wrapper class.
-//
-// Paths searched for libraries are platform and environment-specific.
-// In general...
-//   * On Linux, the LD_LIBRARY_PATH environment variable may be set to a
-//     colon-separated list of directories to search before the standard set.
-//   * On Windows, all directories in the PATH environment variable are checked.
-// Library file names may be relative to the search paths, or absolute.
-// Certain platforms may require the library extension (.so, .dll), or it may
-// be optional. If you know the extension, prefer to include it.
-//
-// Usage:
-//   static const char* kSearchNames[] = {"libfoo.so"};
-//   IREE_ASSIGN_OR_RETURN(library,
-//                         DynamicLibrary::Load(absl::MakeSpan(kSearchNames)));
-//   void* library_symbol_bar = library->GetSymbol("bar");
-//   void* library_symbol_baz = library->GetSymbol("baz");
-class DynamicLibrary {
- public:
-  virtual ~DynamicLibrary() = default;
-
-  // Loads the library at the null-terminated string |search_file_name|.
-  static Status Load(const char* search_file_name,
-                     std::unique_ptr<DynamicLibrary>* out_library) {
-    return Load(absl::Span<const char* const>({search_file_name}), out_library);
-  }
-  // Loads the library at the first name within |search_file_names| found.
-  static Status Load(absl::Span<const char* const> search_file_names,
-                     std::unique_ptr<DynamicLibrary>* out_library);
-
-  // Gets the name of the library file that is loaded.
-  const std::string& file_name() const { return file_name_; }
-
-  // Loads a debug database (PDB/DWARF/etc) from the given path providing debug
-  // symbols for this library and attaches it to the symbol store (if active).
-  virtual void AttachDebugDatabase(const char* database_file_name) {}
-
-  // Gets the address of a symbol with the given name in the loaded library.
-  // Returns NULL if the symbol could not be found.
-  virtual void* GetSymbol(const char* symbol_name) const = 0;
-  template <typename T>
-  T GetSymbol(const char* symbol_name) const {
-    return reinterpret_cast<T>(GetSymbol(symbol_name));
-  }
-
- protected:
-  // Private constructor, use |Load| factory method instead.
-  DynamicLibrary(std::string file_name) : file_name_(file_name) {}
-
-  std::string file_name_;
-};
-
-}  // namespace iree
-
-#endif  // IREE_BASE_DYNAMIC_LIBRARY_H_
diff --git a/iree/base/dynamic_library_posix.cc b/iree/base/dynamic_library_posix.cc
deleted file mode 100644
index 3027a68..0000000
--- a/iree/base/dynamic_library_posix.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "absl/memory/memory.h"
-#include "iree/base/dynamic_library.h"
-#include "iree/base/target_platform.h"
-#include "iree/base/tracing.h"
-
-#if defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_APPLE) || \
-    defined(IREE_PLATFORM_LINUX)
-
-#include <dlfcn.h>
-
-namespace iree {
-
-class DynamicLibraryPosix : public DynamicLibrary {
- public:
-  ~DynamicLibraryPosix() override {
-    // TODO(benvanik): disable if we want to get profiling results.
-    //   Sometimes closing the library can prevent proper symbolization on
-    //   crashes or in sampling profilers.
-    ::dlclose(library_);
-  }
-
-  static Status Load(absl::Span<const char* const> search_file_names,
-                     std::unique_ptr<DynamicLibrary>* out_library) {
-    IREE_TRACE_SCOPE0("DynamicLibraryPosix::Load");
-    out_library->reset();
-
-    for (int i = 0; i < search_file_names.size(); ++i) {
-      void* library = ::dlopen(search_file_names[i], RTLD_LAZY | RTLD_LOCAL);
-      if (library) {
-        out_library->reset(
-            new DynamicLibraryPosix(search_file_names[i], library));
-        return OkStatus();
-      }
-    }
-    return iree_make_status(IREE_STATUS_UNAVAILABLE,
-                            "unable to open dynamic library:'%s'", dlerror());
-  }
-
-  void* GetSymbol(const char* symbol_name) const override {
-    return ::dlsym(library_, symbol_name);
-  }
-
- private:
-  DynamicLibraryPosix(std::string file_name, void* library)
-      : DynamicLibrary(file_name), library_(library) {}
-
-  void* library_;
-};
-
-// static
-Status DynamicLibrary::Load(absl::Span<const char* const> search_file_names,
-                            std::unique_ptr<DynamicLibrary>* out_library) {
-  return DynamicLibraryPosix::Load(search_file_names, out_library);
-}
-
-}  // namespace iree
-
-#endif  // IREE_PLATFORM_*
diff --git a/iree/base/dynamic_library_win32.cc b/iree/base/dynamic_library_win32.cc
deleted file mode 100644
index 804d793..0000000
--- a/iree/base/dynamic_library_win32.cc
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "absl/memory/memory.h"
-#include "absl/strings/str_replace.h"
-#include "iree/base/dynamic_library.h"
-#include "iree/base/internal/file_path.h"
-#include "iree/base/target_platform.h"
-#include "iree/base/tracing.h"
-
-#if defined(IREE_PLATFORM_WINDOWS)
-
-// TODO(benvanik): support PDB overlays when tracy is not enabled too; we'll
-// need to rearrange how the dbghelp lock is handled for that (probably moving
-// it here and having the tracy code redirect to this).
-#if defined(TRACY_ENABLE)
-#define IREE_HAVE_DYNAMIC_LIBRARY_PDB_SUPPORT 1
-#pragma warning(disable : 4091)
-#include <dbghelp.h>
-extern "C" void IREEDbgHelpLock();
-extern "C" void IREEDbgHelpUnlock();
-#endif  // TRACY_ENABLE
-
-namespace iree {
-
-// We need to match the expected paths from dbghelp exactly or else we'll get
-// spurious warnings during module resolution. AFAICT our approach here with
-// loading the PDBs directly with a module base/size of the loaded module will
-// work regardless but calls like SymRefreshModuleList will still attempt to
-// load from system symbol search paths if things don't line up.
-static void CanonicalizePath(std::string* path) {
-  absl::StrReplaceAll({{"/", "\\"}}, path);
-  absl::StrReplaceAll({{"\\\\", "\\"}}, path);
-}
-
-class DynamicLibraryWin : public DynamicLibrary {
- public:
-  ~DynamicLibraryWin() override {
-    IREE_TRACE_SCOPE();
-    // TODO(benvanik): disable if we want to get profiling results.
-    //   Sometimes closing the library can prevent proper symbolization on
-    //   crashes or in sampling profilers.
-    ::FreeLibrary(library_);
-  }
-
-  static Status Load(absl::Span<const char* const> search_file_names,
-                     std::unique_ptr<DynamicLibrary>* out_library) {
-    IREE_TRACE_SCOPE();
-    out_library->reset();
-
-    for (int i = 0; i < search_file_names.size(); ++i) {
-      HMODULE library = ::LoadLibraryA(search_file_names[i]);
-      if (library) {
-        out_library->reset(
-            new DynamicLibraryWin(search_file_names[i], library));
-        return OkStatus();
-      }
-    }
-
-    return iree_make_status(
-        IREE_STATUS_UNAVAILABLE,
-        "unable to open dynamic library, not found on search paths");
-  }
-
-#if defined(IREE_HAVE_DYNAMIC_LIBRARY_PDB_SUPPORT)
-  void AttachDebugDatabase(const char* database_file_name) override {
-    IREE_TRACE_SCOPE();
-
-    // Derive the base module name (path stem) for the loaded module.
-    // For example, the name of 'C:\Dev\foo.dll' would be 'foo'.
-    // This name is used by dbghelp for listing loaded modules and we want to
-    // ensure we match the name of the PDB module with the library module.
-    std::string module_name = file_name_;
-    size_t last_slash = module_name.find_last_of('\\');
-    if (last_slash != std::string::npos) {
-      module_name = module_name.substr(last_slash + 1);
-    }
-    size_t dot = module_name.find_last_of('.');
-    if (dot != std::string::npos) {
-      module_name = module_name.substr(0, dot);
-    }
-
-    IREEDbgHelpLock();
-
-    // Useful for debugging; will print search paths and results:
-    // SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEBUG);
-
-    // Enumerates all loaded modules in the process to extract the module
-    // base/size parameters we need to overlay the PDB. There's other ways to
-    // get this (such as registering a LdrDllNotification callback and snooping
-    // the values during LoadLibrary or using CreateToolhelp32Snapshot), however
-    // EnumerateLoadedModules is in dbghelp which we are using anyway.
-    ModuleEnumCallbackState state;
-    state.module_file_path = file_name_.c_str();
-    EnumerateLoadedModules64(GetCurrentProcess(), EnumLoadedModulesCallback,
-                             &state);
-
-    // Load the PDB file and overlay it onto the already-loaded module at the
-    // address range it got loaded into.
-    if (state.module_base != 0) {
-      SymLoadModuleEx(GetCurrentProcess(), NULL, database_file_name,
-                      module_name.c_str(), state.module_base, state.module_size,
-                      NULL, 0);
-    }
-
-    IREEDbgHelpUnlock();
-  }
-#endif  // IREE_HAVE_DYNAMIC_LIBRARY_PDB_SUPPORT
-
-  void* GetSymbol(const char* symbol_name) const override {
-    return reinterpret_cast<void*>(::GetProcAddress(library_, symbol_name));
-  }
-
- private:
-  DynamicLibraryWin(std::string file_name, HMODULE library)
-      : DynamicLibrary(std::move(file_name)), library_(library) {
-    CanonicalizePath(&file_name_);
-  }
-
-#if defined(IREE_HAVE_DYNAMIC_LIBRARY_PDB_SUPPORT)
-  struct ModuleEnumCallbackState {
-    const char* module_file_path = NULL;
-    DWORD64 module_base = 0;
-    ULONG module_size = 0;
-  };
-  static BOOL EnumLoadedModulesCallback(PCSTR ModuleName, DWORD64 ModuleBase,
-                                        ULONG ModuleSize, PVOID UserContext) {
-    auto* state = reinterpret_cast<ModuleEnumCallbackState*>(UserContext);
-    if (strcmp(ModuleName, state->module_file_path) != 0) {
-      return TRUE;  // not a match; continue
-    }
-    state->module_base = ModuleBase;
-    state->module_size = ModuleSize;
-    return FALSE;  // match found; stop enumeration
-  }
-#endif  // IREE_HAVE_DYNAMIC_LIBRARY_PDB_SUPPORT
-
-  HMODULE library_ = NULL;
-};
-
-// static
-Status DynamicLibrary::Load(absl::Span<const char* const> search_file_names,
-                            std::unique_ptr<DynamicLibrary>* out_library) {
-  return DynamicLibraryWin::Load(search_file_names, out_library);
-}
-
-}  // namespace iree
-
-#endif  // IREE_PLATFORM_*
diff --git a/iree/base/internal/BUILD b/iree/base/internal/BUILD
index efc4dd4..0906894 100644
--- a/iree/base/internal/BUILD
+++ b/iree/base/internal/BUILD
@@ -90,36 +90,29 @@
 )
 
 cc_library(
-    name = "file_handle_win32",
-    srcs = ["file_handle_win32.cc"],
-    hdrs = ["file_handle_win32.h"],
+    name = "dynamic_library",
+    srcs = [
+        "dynamic_library_posix.c",
+        "dynamic_library_win32.c",
+    ],
+    hdrs = ["dynamic_library.h"],
     deps = [
+        ":file_path",
+        ":internal",
         "//iree/base:core_headers",
-        "//iree/base:status",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/strings",
+        "//iree/base:threading",
+        "//iree/base:tracing",
     ],
 )
 
 cc_library(
     name = "file_io",
+    srcs = ["file_io.cc"],
     hdrs = ["file_io.h"],
     deps = [
+        "//iree/base:api",
         "//iree/base:core_headers",
-        "//iree/base:status",
-        "//iree/base/internal:file_io_internal",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
-    ],
-)
-
-cc_library(
-    name = "file_io_hdrs",
-    hdrs = ["file_io.h"],
-    deps = [
-        "//iree/base:status",
-        "@com_google_absl//absl/strings",
+        "//iree/base:tracing",
     ],
 )
 
@@ -128,28 +121,8 @@
     srcs = ["file_io_test.cc"],
     deps = [
         ":file_io",
-        "//iree/base:logging",
-        "//iree/base:status",
         "//iree/testing:gtest",
         "//iree/testing:gtest_main",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
-cc_library(
-    name = "file_io_internal",
-    srcs = [
-        "file_io_posix.cc",
-        "file_io_win32.cc",
-    ],
-    deps = [
-        ":file_handle_win32",
-        ":file_io_hdrs",
-        "//iree/base:core_headers",
-        "//iree/base:status",
-        "//iree/base:tracing",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/strings",
     ],
 )
 
diff --git a/iree/base/internal/CMakeLists.txt b/iree/base/internal/CMakeLists.txt
index 6cf3640..2ab0ae8 100644
--- a/iree/base/internal/CMakeLists.txt
+++ b/iree/base/internal/CMakeLists.txt
@@ -76,16 +76,18 @@
 
 iree_cc_library(
   NAME
-    file_handle_win32
+    dynamic_library
   HDRS
-    "file_handle_win32.h"
+    "dynamic_library.h"
   SRCS
-    "file_handle_win32.cc"
+    "dynamic_library_posix.c"
+    "dynamic_library_win32.c"
   DEPS
-    absl::memory
-    absl::strings
+    ::file_path
+    ::internal
     iree::base::core_headers
-    iree::base::status
+    iree::base::threading
+    iree::base::tracing
   PUBLIC
 )
 
@@ -94,24 +96,12 @@
     file_io
   HDRS
     "file_io.h"
+  SRCS
+    "file_io.cc"
   DEPS
-    absl::memory
-    absl::span
-    absl::strings
+    iree::base::api
     iree::base::core_headers
-    iree::base::internal::file_io_internal
-    iree::base::status
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
-    file_io_hdrs
-  HDRS
-    "file_io.h"
-  DEPS
-    absl::strings
-    iree::base::status
+    iree::base::tracing
   PUBLIC
 )
 
@@ -122,32 +112,12 @@
     "file_io_test.cc"
   DEPS
     ::file_io
-    absl::strings
-    iree::base::logging
-    iree::base::status
     iree::testing::gtest
     iree::testing::gtest_main
 )
 
 iree_cc_library(
   NAME
-    file_io_internal
-  SRCS
-    "file_io_posix.cc"
-    "file_io_win32.cc"
-  DEPS
-    ::file_handle_win32
-    ::file_io_hdrs
-    absl::memory
-    absl::strings
-    iree::base::core_headers
-    iree::base::status
-    iree::base::tracing
-  PUBLIC
-)
-
-iree_cc_library(
-  NAME
     file_path
   HDRS
     "file_path.h"
diff --git a/iree/base/internal/dynamic_library.h b/iree/base/internal/dynamic_library.h
new file mode 100644
index 0000000..eff168a
--- /dev/null
+++ b/iree/base/internal/dynamic_library.h
@@ -0,0 +1,86 @@
+// Copyright 2021 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_BASE_INTERNAL_DYNAMIC_LIBRARY_H_
+#define IREE_BASE_INTERNAL_DYNAMIC_LIBRARY_H_
+
+#include "iree/base/api.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Defines the behavior of the dynamic library loader.
+enum iree_dynamic_library_flags_e {
+  IREE_DYNAMIC_LIBRARY_FLAG_NONE = 0u,
+};
+typedef uint32_t iree_dynamic_library_flags_t;
+
+// Dynamic library (aka shared object) cross-platform wrapper.
+typedef struct iree_dynamic_library_s iree_dynamic_library_t;
+
+// Loads a system library using both the system library load paths and the given
+// file name. The path may may be absolute or relative.
+//
+// For process-wide search control the LD_LIBRARY_PATH (Linux) or PATH (Windows)
+// is used in addition to the default search path rules of the platform.
+iree_status_t iree_dynamic_library_load_from_file(
+    const char* file_path, iree_dynamic_library_flags_t flags,
+    iree_allocator_t allocator, iree_dynamic_library_t** out_library);
+
+// Loads a system library using both the system library load paths and the given
+// search path/alternative file names. The paths may may be absolute or
+// relative.
+//
+// For process-wide search control the LD_LIBRARY_PATH (Linux) or PATH (Windows)
+// is used in addition to the default search path rules of the platform.
+iree_status_t iree_dynamic_library_load_from_files(
+    iree_host_size_t search_path_count, const char* const* search_paths,
+    iree_dynamic_library_flags_t flags, iree_allocator_t allocator,
+    iree_dynamic_library_t** out_library);
+
+// Opens a dynamic library from a range of bytes in memory.
+// |identifier| will be used as the module name in debugging/profiling tools.
+// |buffer| must remain live for the lifetime of the library.
+iree_status_t iree_dynamic_library_load_from_memory(
+    iree_string_view_t identifier, iree_const_byte_span_t buffer,
+    iree_dynamic_library_flags_t flags, iree_allocator_t allocator,
+    iree_dynamic_library_t** out_library);
+
+// Retains the given |library| for the caller.
+void iree_dynamic_library_retain(iree_dynamic_library_t* library);
+
+// Releases the given |library| from the caller.
+void iree_dynamic_library_release(iree_dynamic_library_t* library);
+
+// Performs a symbol lookup in the dynamic library exports.
+iree_status_t iree_dynamic_library_lookup_symbol(
+    iree_dynamic_library_t* library, const char* symbol_name, void** out_fn);
+
+// Loads a debug database (PDB/DWARF/etc) from the given path providing debug
+// symbols for this library and attaches it to the symbol store (if active).
+iree_status_t iree_dynamic_library_attach_symbols_from_file(
+    iree_dynamic_library_t* library, const char* file_path);
+
+// Loads a debug database (PDB/DWARF/etc) from a range of bytes in memory and
+// attaches it to the symbol store (if active). |buffer| must remain live for
+// the lifetime of the library.
+iree_status_t iree_dynamic_library_attach_symbols_from_memory(
+    iree_dynamic_library_t* library, iree_const_byte_span_t buffer);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // IREE_BASE_INTERNAL_DYNAMIC_LIBRARY_H_
diff --git a/iree/base/internal/dynamic_library_posix.c b/iree/base/internal/dynamic_library_posix.c
new file mode 100644
index 0000000..91e81c4
--- /dev/null
+++ b/iree/base/internal/dynamic_library_posix.c
@@ -0,0 +1,286 @@
+// Copyright 2021 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 <stdio.h>
+
+#include "iree/base/internal/atomics.h"
+#include "iree/base/internal/dynamic_library.h"
+#include "iree/base/internal/file_path.h"
+#include "iree/base/target_platform.h"
+#include "iree/base/tracing.h"
+
+#if defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_APPLE) || \
+    defined(IREE_PLATFORM_LINUX)
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+struct iree_dynamic_library_s {
+  iree_atomic_ref_count_t ref_count;
+  iree_allocator_t allocator;
+
+  // dlopen shared object handle.
+  void* handle;
+};
+
+// Allocate a new string from |allocator| returned in |out_file_path| containing
+// a path to a unique file on the filesystem.
+static iree_status_t iree_dynamic_library_make_temp_file_path(
+    const char* prefix, const char* extension, iree_allocator_t allocator,
+    char** out_file_path) {
+  // Query the 'TMPDIR' environment variable to allow users to override the
+  // path.
+  const char* tmpdir = getenv("TMPDIR");
+  if (!tmpdir) {
+#ifdef __ANDROID__
+    // Support running Android command-line programs both as regular shell user
+    // and as root. For the latter, TMPDIR is not defined by default.
+    tmpdir = "/data/local/tmp";
+#else
+    tmpdir = "/tmp";
+#endif  // __ANDROID__
+  }
+
+  // Stamp in a unique file name (replacing XXXXXX in the string).
+  char temp_path[512];
+  if (snprintf(temp_path, sizeof(temp_path), "%s/iree_dylib_XXXXXX", tmpdir) >=
+      sizeof(temp_path)) {
+    // NOTE: we could dynamically allocate things, but didn't seem worth it.
+    return iree_make_status(
+        IREE_STATUS_INVALID_ARGUMENT,
+        "TMPDIR name too long (>%zu chars); keep it reasonable",
+        sizeof(temp_path));
+  }
+  int fd = mkstemp(temp_path);
+  if (fd < 0) {
+    return iree_make_status(iree_status_code_from_errno(errno),
+                            "unable to mkstemp file");
+  }
+
+  // Allocate storage for the full file path and format it in.
+  int file_path_length =
+      snprintf(NULL, 0, "%s_%s.%s", temp_path, prefix, extension);
+  if (file_path_length < 0) {
+    return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
+                            "unable to form temp path string");
+  }
+  IREE_RETURN_IF_ERROR(iree_allocator_malloc(
+      allocator, file_path_length + /*NUL=*/1, (void**)out_file_path));
+  snprintf(*out_file_path, file_path_length + /*NUL=*/1, "%s_%s.%s", temp_path,
+           prefix, extension);
+
+  // Canonicalize away any double path separators.
+  iree_file_path_canonicalize(*out_file_path, file_path_length);
+
+  return iree_ok_status();
+}
+
+// Creates a temp file and writes the |source_data| into it.
+// The file path is returned in |out_file_path|.
+static iree_status_t iree_dynamic_library_write_temp_file(
+    iree_const_byte_span_t source_data, const char* prefix,
+    const char* extension, iree_allocator_t allocator, char** out_file_path) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  // Reserve a temp file path we can write to.
+  IREE_RETURN_AND_END_ZONE_IF_ERROR(
+      z0, iree_dynamic_library_make_temp_file_path(prefix, extension, allocator,
+                                                   out_file_path));
+
+  iree_status_t status = iree_ok_status();
+
+  // Open the file for writing.
+  FILE* file_handle = fopen(*out_file_path, "wb");
+  if (file_handle == NULL) {
+    status = iree_make_status(iree_status_code_from_errno(errno),
+                              "unable to open file '%s'", *out_file_path);
+  }
+
+  // Write all file bytes.
+  if (iree_status_is_ok(status)) {
+    if (fwrite((char*)source_data.data, source_data.data_length, 1,
+               file_handle) != 1) {
+      status =
+          iree_make_status(iree_status_code_from_errno(errno),
+                           "unable to write file span of %zu bytes to '%s'",
+                           source_data.data_length, *out_file_path);
+    }
+  }
+
+  if (file_handle != NULL) {
+    fclose(file_handle);
+    file_handle = NULL;
+  }
+  if (!iree_status_is_ok(status)) {
+    iree_allocator_free(allocator, *out_file_path);
+  }
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+// Allocates an iree_dynamic_library_t with the given allocator.
+static iree_status_t iree_dynamic_library_create(
+    void* handle, iree_allocator_t allocator,
+    iree_dynamic_library_t** out_library) {
+  *out_library = NULL;
+
+  iree_dynamic_library_t* library = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_allocator_malloc(allocator, sizeof(*library), (void**)&library));
+  memset(library, 0, sizeof(*library));
+  iree_atomic_ref_count_init(&library->ref_count);
+  library->allocator = allocator;
+  library->handle = handle;
+
+  *out_library = library;
+  return iree_ok_status();
+}
+
+iree_status_t iree_dynamic_library_load_from_file(
+    const char* file_path, iree_dynamic_library_flags_t flags,
+    iree_allocator_t allocator, iree_dynamic_library_t** out_library) {
+  return iree_dynamic_library_load_from_files(1, &file_path, flags, allocator,
+                                              out_library);
+}
+
+iree_status_t iree_dynamic_library_load_from_files(
+    iree_host_size_t search_path_count, const char* const* search_paths,
+    iree_dynamic_library_flags_t flags, iree_allocator_t allocator,
+    iree_dynamic_library_t** out_library) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  IREE_ASSERT_ARGUMENT(out_library);
+  *out_library = NULL;
+
+  // Try to load the module from the set of search paths provided.
+  void* handle = NULL;
+  iree_host_size_t i = 0;
+  for (i = 0; i < search_path_count; ++i) {
+    handle = dlopen(search_paths[i], RTLD_LAZY | RTLD_LOCAL);
+    if (handle) break;
+  }
+  if (!handle) {
+    IREE_TRACE_ZONE_END(z0);
+    return iree_make_status(IREE_STATUS_NOT_FOUND,
+                            "dynamic library not found on any search path");
+  }
+
+  iree_dynamic_library_t* library = NULL;
+  iree_status_t status =
+      iree_dynamic_library_create(handle, allocator, &library);
+
+  if (iree_status_is_ok(status)) {
+    *out_library = library;
+  } else {
+    dlclose(handle);
+  }
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+// TODO(#3845): use dlopen on an fd with either dlopen(/proc/self/fd/NN),
+// fdlopen, or android_dlopen_ext to avoid needing to write the file to disk.
+// Can fallback to memfd_create + dlopen where available, and fallback from
+// that to disk (maybe just windows/mac).
+iree_status_t iree_dynamic_library_load_from_memory(
+    iree_string_view_t identifier, iree_const_byte_span_t buffer,
+    iree_dynamic_library_flags_t flags, iree_allocator_t allocator,
+    iree_dynamic_library_t** out_library) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  IREE_ASSERT_ARGUMENT(out_library);
+  *out_library = NULL;
+
+  // Extract the library to a temp file.
+  char* temp_path = NULL;
+  IREE_RETURN_AND_END_ZONE_IF_ERROR(
+      z0, iree_dynamic_library_write_temp_file(buffer, "mem_", "so", allocator,
+                                               &temp_path));
+
+  // Load using the normal load from file routine.
+  iree_status_t status = iree_dynamic_library_load_from_file(
+      temp_path, flags, allocator, out_library);
+
+  // Unlink the temp file - it's still open by the loader but won't be
+  // accessible to anyone else and will be deleted once the library is
+  // unloaded.
+  remove(temp_path);
+  iree_allocator_free(allocator, temp_path);
+
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+static void iree_dynamic_library_delete(iree_dynamic_library_t* library) {
+  iree_allocator_t allocator = library->allocator;
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+#if IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
+  // Leak the library when tracing, since the profiler may still be reading it.
+  // TODO(benvanik): move to an atexit handler instead, verify with ASAN/MSAN
+  // TODO(scotttodd): Make this compatible with testing:
+  //     two test cases, one for each function in the same executable
+  //     first test case passes, second fails to open the file (already open)
+#else
+  // Close the library first as it may be loaded from one of the temp files we
+  // are about to delete.
+  if (library->handle != NULL) {
+    dlclose(library->handle);
+  }
+#endif  // IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
+
+  iree_allocator_free(allocator, library);
+
+  IREE_TRACE_ZONE_END(z0);
+}
+
+void iree_dynamic_library_retain(iree_dynamic_library_t* library) {
+  if (library) {
+    iree_atomic_ref_count_inc(&library->ref_count);
+  }
+}
+
+void iree_dynamic_library_release(iree_dynamic_library_t* library) {
+  if (library && iree_atomic_ref_count_dec(&library->ref_count) == 1) {
+    iree_dynamic_library_delete(library);
+  }
+}
+
+iree_status_t iree_dynamic_library_lookup_symbol(
+    iree_dynamic_library_t* library, const char* symbol_name, void** out_fn) {
+  IREE_ASSERT_ARGUMENT(library);
+  IREE_ASSERT_ARGUMENT(symbol_name);
+  IREE_ASSERT_ARGUMENT(out_fn);
+  *out_fn = NULL;
+  void* fn = dlsym(library->handle, symbol_name);
+  if (!fn) {
+    return iree_make_status(IREE_STATUS_NOT_FOUND,
+                            "symbol '%s' not found in library", symbol_name);
+  }
+  *out_fn = fn;
+  return iree_ok_status();
+}
+
+iree_status_t iree_dynamic_library_attach_symbols_from_file(
+    iree_dynamic_library_t* library, const char* file_path) {
+  return iree_ok_status();
+}
+
+iree_status_t iree_dynamic_library_attach_symbols_from_memory(
+    iree_dynamic_library_t* library, iree_const_byte_span_t buffer) {
+  return iree_ok_status();
+}
+
+#endif  // IREE_PLATFORM_*
diff --git a/iree/base/internal/dynamic_library_win32.c b/iree/base/internal/dynamic_library_win32.c
new file mode 100644
index 0000000..0f21772
--- /dev/null
+++ b/iree/base/internal/dynamic_library_win32.c
@@ -0,0 +1,425 @@
+// Copyright 2021 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 <stdio.h>
+
+#include "iree/base/internal/atomics.h"
+#include "iree/base/internal/dynamic_library.h"
+#include "iree/base/internal/file_path.h"
+#include "iree/base/target_platform.h"
+#include "iree/base/threading.h"
+#include "iree/base/tracing.h"
+
+#if defined(IREE_PLATFORM_WINDOWS)
+
+// TODO(benvanik): support PDB overlays when tracy is not enabled; we'll
+// need to rearrange how the dbghelp lock is handled for that (probably moving
+// it here and having the tracy code redirect to this).
+#if defined(TRACY_ENABLE)
+#define IREE_HAVE_DYNAMIC_LIBRARY_PDB_SUPPORT 1
+#pragma warning(disable : 4091)
+#include <dbghelp.h>
+extern "C" void IREEDbgHelpLock();
+extern "C" void IREEDbgHelpUnlock();
+#endif  // TRACY_ENABLE
+
+struct iree_dynamic_library_s {
+  iree_atomic_ref_count_t ref_count;
+  iree_allocator_t allocator;
+
+  // Base module name used as an identifier. When loaded from a file this must
+  // be the basename for dbghelp to be able to find symbols.
+  // Owned and allocated as part of the struct upon creation.
+  // Has NUL terminator for compatibility with Windows APIs.
+  char* identifier;
+
+  // File path of the loaded module, if loaded from one.
+  // Owned and allocated as part of the struct upon creation.
+  // Has NUL terminator for compatibility with Windows APIs.
+  char* module_path;
+
+  // Windows module handle.
+  HMODULE module;
+
+  // 0 or more file paths that were created as part of the loading of the
+  // library or attaching of symbols from memory.
+  //
+  // Each path string is allocated using the |allocator| and freed during
+  // library deletion.
+  iree_host_size_t temp_file_count;
+  char* temp_file_paths[2];
+};
+
+static iree_once_flag iree_dynamic_library_temp_path_flag_ =
+    IREE_ONCE_FLAG_INIT;
+static char iree_dynamic_library_temp_path_base_[MAX_PATH + 1];
+static void iree_dynamic_library_init_temp_paths(void) {
+  // Query the temp path from the OS. This can be overridden with the following
+  // environment variables: [TMP, TEMP, USERPROFILE].
+  //
+  // See:
+  // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppatha
+  char temp_path[MAX_PATH];
+  DWORD temp_path_length = GetTempPathA(IREE_ARRAYSIZE(temp_path), temp_path);
+
+  // Append the process ID to the path; this is like what _mktemp does but
+  // without all the hoops.
+  snprintf(iree_dynamic_library_temp_path_base_,
+           sizeof(iree_dynamic_library_temp_path_base_), "%s\\iree_dylib_%08X",
+           temp_path, GetCurrentProcessId());
+
+  // Canonicalize away any double path separators.
+  iree_file_path_canonicalize(iree_dynamic_library_temp_path_base_,
+                              strlen(iree_dynamic_library_temp_path_base_));
+}
+
+// Allocate a new string from |allocator| returned in |out_file_path| containing
+// a path to a unique file on the filesystem.
+static iree_status_t iree_dynamic_library_make_temp_file_path(
+    const char* prefix, const char* extension, iree_allocator_t allocator,
+    char** out_file_path) {
+  // Ensure the root temp paths are queried/initialized.
+  iree_call_once(&iree_dynamic_library_temp_path_flag_,
+                 iree_dynamic_library_init_temp_paths);
+
+  // Generate a per-file unique identifier only unique **within** the current
+  // process. We combine this with the _mktemp path that should be unique to the
+  // process itself.
+  static iree_atomic_int32_t next_unique_id = IREE_ATOMIC_VAR_INIT(0);
+  uint32_t unique_id = (uint32_t)iree_atomic_fetch_add_int32(
+      &next_unique_id, 1, iree_memory_order_seq_cst);
+
+  // Allocate storage for the full file path and format it in.
+  int file_path_length =
+      snprintf(NULL, 0, "%s_%s_%08X.%s", iree_dynamic_library_temp_path_base_,
+               prefix, unique_id, extension);
+  if (file_path_length < 0) {
+    return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
+                            "unable to form temp path string");
+  }
+  IREE_RETURN_IF_ERROR(iree_allocator_malloc(
+      allocator, file_path_length + /*NUL=*/1, (void**)out_file_path));
+  snprintf(*out_file_path, file_path_length + /*NUL=*/1, "%s_%s_%08X.%s",
+           iree_dynamic_library_temp_path_base_, prefix, unique_id, extension);
+
+  return iree_ok_status();
+}
+
+// Creates a temp file and writes the |source_data| into it.
+// The file path is returned in |out_file_path|.
+static iree_status_t iree_dynamic_library_write_temp_file(
+    iree_const_byte_span_t source_data, const char* prefix,
+    const char* extension, iree_allocator_t allocator, char** out_file_path) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  // Reserve a temp file path we can write to.
+  IREE_RETURN_AND_END_ZONE_IF_ERROR(
+      z0, iree_dynamic_library_make_temp_file_path(prefix, extension, allocator,
+                                                   out_file_path));
+
+  iree_status_t status = iree_ok_status();
+
+  // Open the file for writing.
+  HANDLE file_handle = CreateFileA(
+      /*lpFileName=*/*out_file_path, /*dwDesiredAccess=*/GENERIC_WRITE,
+      /*dwShareMode=*/FILE_SHARE_DELETE, /*lpSecurityAttributes=*/NULL,
+      /*dwCreationDisposition=*/CREATE_ALWAYS,
+      /*dwFlagsAndAttributes=*/FILE_ATTRIBUTE_TEMPORARY |
+          FILE_FLAG_DELETE_ON_CLOSE,
+      /*hTemplateFile=*/NULL);
+  if (file_handle == INVALID_HANDLE_VALUE) {
+    status = iree_make_status(iree_status_code_from_win32_error(GetLastError()),
+                              "unable to open file '%s'", *out_file_path);
+  }
+
+  // Write all file bytes.
+  if (iree_status_is_ok(status)) {
+    if (WriteFile(file_handle, source_data.data, (DWORD)source_data.data_length,
+                  NULL, NULL) == FALSE) {
+      status =
+          iree_make_status(iree_status_code_from_win32_error(GetLastError()),
+                           "unable to write file span of %zu bytes to '%s'",
+                           source_data.data_length, *out_file_path);
+    }
+  }
+
+  if (file_handle != NULL) {
+    CloseHandle(file_handle);
+    file_handle = NULL;
+  }
+  if (!iree_status_is_ok(status)) {
+    iree_allocator_free(allocator, *out_file_path);
+  }
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+// Allocates an iree_dynamic_library_t with the given allocator.
+static iree_status_t iree_dynamic_library_create(
+    iree_string_view_t identifier, iree_string_view_t module_path,
+    HMODULE module, iree_allocator_t allocator,
+    iree_dynamic_library_t** out_library) {
+  *out_library = NULL;
+
+  iree_dynamic_library_t* library = NULL;
+  iree_host_size_t total_size =
+      sizeof(*library) + (identifier.size + 1) + (module_path.size + 1);
+  IREE_RETURN_IF_ERROR(
+      iree_allocator_malloc(allocator, total_size, (void**)&library));
+  memset(library, 0, total_size);
+  iree_atomic_ref_count_init(&library->ref_count);
+  library->allocator = allocator;
+  library->module = module;
+
+  library->identifier = (char*)library + sizeof(*library);
+  memcpy(library->identifier, identifier.data, identifier.size);
+  library->identifier[identifier.size] = 0;  // NUL
+
+  library->module_path = library->identifier + (identifier.size + 1);
+  memcpy(library->module_path, module_path.data, module_path.size);
+  library->module_path[module_path.size] = 0;  // NUL
+
+  *out_library = library;
+  return iree_ok_status();
+}
+
+iree_status_t iree_dynamic_library_load_from_file(
+    const char* file_path, iree_dynamic_library_flags_t flags,
+    iree_allocator_t allocator, iree_dynamic_library_t** out_library) {
+  return iree_dynamic_library_load_from_files(1, &file_path, flags, allocator,
+                                              out_library);
+}
+
+iree_status_t iree_dynamic_library_load_from_files(
+    iree_host_size_t search_path_count, const char* const* search_paths,
+    iree_dynamic_library_flags_t flags, iree_allocator_t allocator,
+    iree_dynamic_library_t** out_library) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  IREE_ASSERT_ARGUMENT(out_library);
+  *out_library = NULL;
+
+  // Try to load the module from the set of search paths provided.
+  HMODULE module = NULL;
+  iree_host_size_t i = 0;
+  for (i = 0; i < search_path_count; ++i) {
+    module = LoadLibraryA(search_paths[i]);
+    if (module) break;
+  }
+  if (!module) {
+    IREE_TRACE_ZONE_END(z0);
+    return iree_make_status(IREE_STATUS_NOT_FOUND,
+                            "dynamic library not found on any search path");
+  }
+
+  iree_string_view_t file_path = iree_make_cstring_view(search_paths[i]);
+  iree_string_view_t identifier = iree_file_path_basename(file_path);
+
+  iree_dynamic_library_t* library = NULL;
+  iree_status_t status = iree_dynamic_library_create(
+      identifier, file_path, module, allocator, &library);
+
+  if (iree_status_is_ok(status)) {
+    *out_library = library;
+  } else {
+    FreeLibrary(module);
+  }
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+iree_status_t iree_dynamic_library_load_from_memory(
+    iree_string_view_t identifier, iree_const_byte_span_t buffer,
+    iree_dynamic_library_flags_t flags, iree_allocator_t allocator,
+    iree_dynamic_library_t** out_library) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  IREE_ASSERT_ARGUMENT(out_library);
+  *out_library = NULL;
+
+  // Extract the library to a temp file.
+  char* temp_path = NULL;
+  iree_status_t status = iree_dynamic_library_write_temp_file(
+      buffer, "mem_", "dll", allocator, &temp_path);
+
+  if (iree_status_is_ok(status)) {
+    // Load using the normal load from file routine.
+    status = iree_dynamic_library_load_from_file(temp_path, flags, allocator,
+                                                 out_library);
+  }
+  if (iree_status_is_ok(status)) {
+    // Associate the temp path to the library; the temp_path string and the
+    // backing file will be deleted when the library is closed.
+    iree_dynamic_library_t* library = *out_library;
+    library->temp_file_paths[library->temp_file_count++] = temp_path;
+  } else {
+    iree_allocator_free(allocator, temp_path);
+  }
+
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+static void iree_dynamic_library_delete(iree_dynamic_library_t* library) {
+  iree_allocator_t allocator = library->allocator;
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+#if IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
+  // Leak the library when tracing, since the profiler may still be reading it.
+  // TODO(benvanik): move to an atexit handler instead, verify with ASAN/MSAN
+  // TODO(scotttodd): Make this compatible with testing:
+  //     two test cases, one for each function in the same executable
+  //     first test case passes, second fails to open the file (already open)
+#else
+  // Close the library first as it may be loaded from one of the temp files we
+  // are about to delete.
+  if (library->module != NULL) {
+    FreeLibrary(library->module);
+  }
+#endif  // IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
+
+  // Cleanup all temp files.
+  for (iree_host_size_t i = 0; i < library->temp_file_count; ++i) {
+    char* file_path = library->temp_file_paths[i];
+    DeleteFileA(file_path);
+    iree_allocator_free(allocator, file_path);
+  }
+
+  iree_allocator_free(allocator, library);
+
+  IREE_TRACE_ZONE_END(z0);
+}
+
+void iree_dynamic_library_retain(iree_dynamic_library_t* library) {
+  if (library) {
+    iree_atomic_ref_count_inc(&library->ref_count);
+  }
+}
+
+void iree_dynamic_library_release(iree_dynamic_library_t* library) {
+  if (library && iree_atomic_ref_count_dec(&library->ref_count) == 1) {
+    iree_dynamic_library_delete(library);
+  }
+}
+
+iree_status_t iree_dynamic_library_lookup_symbol(
+    iree_dynamic_library_t* library, const char* symbol_name, void** out_fn) {
+  IREE_ASSERT_ARGUMENT(library);
+  IREE_ASSERT_ARGUMENT(symbol_name);
+  IREE_ASSERT_ARGUMENT(out_fn);
+  *out_fn = NULL;
+  void* fn = GetProcAddress(library->module, symbol_name);
+  if (!fn) {
+    return iree_make_status(IREE_STATUS_NOT_FOUND,
+                            "symbol '%s' not found in library", symbol_name);
+  }
+  *out_fn = fn;
+  return iree_ok_status();
+}
+
+#if defined(IREE_HAVE_DYNAMIC_LIBRARY_PDB_SUPPORT)
+
+typedef struct {
+  const char* module_path;
+  DWORD64 module_base;
+  ULONG module_size;
+} ModuleEnumCallbackState;
+
+static BOOL EnumLoadedModulesCallback(PCSTR ModuleName, DWORD64 ModuleBase,
+                                      ULONG ModuleSize, PVOID UserContext) {
+  ModuleEnumCallbackState* state = (ModuleEnumCallbackState*)UserContext;
+  if (strcmp(ModuleName, state->module_path) != 0) {
+    return TRUE;  // not a match; continue
+  }
+  state->module_base = ModuleBase;
+  state->module_size = ModuleSize;
+  return FALSE;  // match found; stop enumeration
+}
+
+iree_status_t iree_dynamic_library_attach_symbols_from_file(
+    iree_dynamic_library_t* library, const char* file_path) {
+  IREE_ASSERT_ARGUMENT(library);
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  IREEDbgHelpLock();
+
+  // Useful for debugging this logic; will print search paths and results:
+  // SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEBUG);
+
+  // Enumerates all loaded modules in the process to extract the module
+  // base/size parameters we need to overlay the PDB. There's other ways to
+  // get this (such as registering a LdrDllNotification callback and snooping
+  // the values during LoadLibrary or using CreateToolhelp32Snapshot), however
+  // EnumerateLoadedModules is in dbghelp which we are using anyway.
+  ModuleEnumCallbackState state;
+  memset(&state, 0, sizeof(state));
+  state.module_path = library->module_path;
+  EnumerateLoadedModules64(GetCurrentProcess(), EnumLoadedModulesCallback,
+                           &state);
+
+  // Load the PDB file and overlay it onto the already-loaded module at the
+  // address range it got loaded into.
+  if (state.module_base != 0) {
+    SymLoadModuleEx(GetCurrentProcess(), NULL, file_path,
+                    library->identifier.data, state.module_base,
+                    state.module_size, NULL, 0);
+  }
+
+  IREEDbgHelpUnlock();
+
+  IREE_TRACE_ZONE_END(z0);
+  return iree_ok_status();
+}
+
+iree_status_t iree_dynamic_library_attach_symbols_from_memory(
+    iree_dynamic_library_t* library, iree_const_byte_span_t buffer) {
+  IREE_ASSERT_ARGUMENT(library);
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  if (library->temp_file_count + 1 >=
+      IREE_ARRAYSIZE(library->temp_file_paths)) {
+    return iree_make_status(IREE_STATUS_RESOURCE_EXHAUSTED,
+                            "too many temp files attached");
+  }
+
+  // Extract the library to a temp file.
+  char* temp_path = NULL;
+  iree_status_t status = iree_dynamic_library_write_temp_file(
+      buffer, "mem_", "pdb", library->allocator, &temp_path);
+  if (iree_status_is_ok(status)) {
+    // Associate the temp path to the library; the temp_path string and the
+    // backing file will be deleted when the library is closed.
+    library->temp_file_paths[library->temp_file_count++] = temp_path;
+
+    // Attempt to attach the extracted temp file to the module.
+    status = iree_dynamic_library_attach_symbols_from_file(library, temp_path);
+  }
+
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+#else
+
+iree_status_t iree_dynamic_library_attach_symbols_from_file(
+    iree_dynamic_library_t* library, const char* file_path) {
+  return iree_ok_status();
+}
+
+iree_status_t iree_dynamic_library_attach_symbols_from_memory(
+    iree_dynamic_library_t* library, iree_const_byte_span_t buffer) {
+  return iree_ok_status();
+}
+
+#endif  // IREE_HAVE_DYNAMIC_LIBRARY_PDB_SUPPORT
+
+#endif  // IREE_PLATFORM_WINDOWS
diff --git a/iree/base/internal/file_handle_win32.cc b/iree/base/internal/file_handle_win32.cc
deleted file mode 100644
index 06f0b77..0000000
--- a/iree/base/internal/file_handle_win32.cc
+++ /dev/null
@@ -1,80 +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/base/internal/file_handle_win32.h"
-
-#include "absl/memory/memory.h"
-#include "absl/strings/str_replace.h"
-#include "iree/base/target_platform.h"
-
-#if defined(IREE_PLATFORM_WINDOWS)
-
-namespace iree {
-
-static void CanonicalizePath(std::string* path) {
-  absl::StrReplaceAll({{"/", "\\"}}, path);
-}
-
-// static
-Status FileHandle::OpenRead(std::string path, DWORD file_flags,
-                            std::unique_ptr<FileHandle>* out_handle) {
-  out_handle->reset();
-  CanonicalizePath(&path);
-  HANDLE handle = ::CreateFileA(
-      /*lpFileName=*/path.c_str(), /*dwDesiredAccess=*/GENERIC_READ,
-      /*dwShareMode=*/FILE_SHARE_READ, /*lpSecurityAttributes=*/nullptr,
-      /*dwCreationDisposition=*/OPEN_EXISTING,
-      /*dwFlagsAndAttributes=*/FILE_ATTRIBUTE_NORMAL | file_flags,
-      /*hTemplateFile=*/nullptr);
-  if (handle == INVALID_HANDLE_VALUE) {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to open file '%s'", path.c_str());
-  }
-
-  BY_HANDLE_FILE_INFORMATION file_info;
-  if (::GetFileInformationByHandle(handle, &file_info) == FALSE) {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to query file info for %s", path.c_str());
-  }
-
-  uint64_t file_size = (static_cast<uint64_t>(file_info.nFileSizeHigh) << 32) |
-                       file_info.nFileSizeLow;
-  *out_handle = absl::make_unique<FileHandle>(handle, file_size);
-  return OkStatus();
-}
-
-// static
-Status FileHandle::OpenWrite(std::string path, DWORD file_flags,
-                             std::unique_ptr<FileHandle>* out_handle) {
-  out_handle->reset();
-  CanonicalizePath(&path);
-  HANDLE handle = ::CreateFileA(
-      /*lpFileName=*/path.c_str(), /*dwDesiredAccess=*/GENERIC_WRITE,
-      /*dwShareMode=*/FILE_SHARE_DELETE, /*lpSecurityAttributes=*/nullptr,
-      /*dwCreationDisposition=*/CREATE_ALWAYS,
-      /*dwFlagsAndAttributes=*/FILE_ATTRIBUTE_NORMAL | file_flags,
-      /*hTemplateFile=*/nullptr);
-  if (handle == INVALID_HANDLE_VALUE) {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to open file '%s'", path.c_str());
-  }
-  *out_handle = absl::make_unique<FileHandle>(handle, 0);
-  return OkStatus();
-}
-
-FileHandle::~FileHandle() { ::CloseHandle(handle_); }
-
-}  // namespace iree
-
-#endif  // IREE_PLATFORM_WINDOWS
diff --git a/iree/base/internal/file_handle_win32.h b/iree/base/internal/file_handle_win32.h
deleted file mode 100644
index 3532b59..0000000
--- a/iree/base/internal/file_handle_win32.h
+++ /dev/null
@@ -1,57 +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_BASE_INTERNAL_FILE_HANDLE_WIN32_H_
-#define IREE_BASE_INTERNAL_FILE_HANDLE_WIN32_H_
-
-#include <memory>
-#include <string>
-
-#include "absl/memory/memory.h"
-#include "absl/strings/string_view.h"
-#include "iree/base/status.h"
-#include "iree/base/target_platform.h"
-
-#if defined(IREE_PLATFORM_WINDOWS)
-
-namespace iree {
-
-class FileHandle {
- public:
-  static Status OpenRead(std::string path, DWORD file_flags,
-                         std::unique_ptr<FileHandle>* out_handle);
-  static Status OpenWrite(std::string path, DWORD file_flags,
-                          std::unique_ptr<FileHandle>* out_handle);
-
-  FileHandle(HANDLE handle, size_t size) : handle_(handle), size_(size) {}
-  ~FileHandle();
-
-  absl::string_view path() const { return path_; }
-  HANDLE handle() const { return handle_; }
-  size_t size() const { return size_; }
-
- private:
-  FileHandle(const FileHandle&) = delete;
-  FileHandle& operator=(const FileHandle&) = delete;
-
-  std::string path_;
-  HANDLE handle_;
-  size_t size_;
-};
-
-}  // namespace iree
-
-#endif  // IREE_PLATFORM_WINDOWS
-
-#endif  // IREE_BASE_INTERNAL_FILE_HANDLE_WIN32_H_
diff --git a/iree/base/internal/file_io.cc b/iree/base/internal/file_io.cc
new file mode 100644
index 0000000..4190e0d
--- /dev/null
+++ b/iree/base/internal/file_io.cc
@@ -0,0 +1,105 @@
+// 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/base/internal/file_io.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "iree/base/target_platform.h"
+#include "iree/base/tracing.h"
+
+namespace iree {
+namespace file_io {
+
+iree_status_t FileExists(const char* path) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  struct stat stat_buf;
+  iree_status_t status =
+      stat(path, &stat_buf) == 0
+          ? iree_ok_status()
+          : iree_make_status(IREE_STATUS_NOT_FOUND, "'%s'", path);
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+iree_status_t GetFileContents(const char* path, std::string* out_contents) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  *out_contents = std::string();
+  FILE* file = fopen(path, "r");
+  if (file == NULL) {
+    IREE_TRACE_ZONE_END(z0);
+    return iree_make_status(iree_status_code_from_errno(errno),
+                            "failed to open file '%s'", path);
+  }
+  iree_status_t status = iree_ok_status();
+  if (fseek(file, 0, SEEK_END) == -1) {
+    status = iree_make_status(iree_status_code_from_errno(errno), "seek (end)");
+  }
+  size_t file_size = 0;
+  if (iree_status_is_ok(status)) {
+    file_size = ftell(file);
+    if (file_size == -1L) {
+      status =
+          iree_make_status(iree_status_code_from_errno(errno), "size query");
+    }
+  }
+  if (iree_status_is_ok(status)) {
+    if (fseek(file, 0, SEEK_SET) == -1) {
+      status =
+          iree_make_status(iree_status_code_from_errno(errno), "seek (beg)");
+    }
+  }
+  std::string contents;
+  if (iree_status_is_ok(status)) {
+    contents.resize(file_size);
+    if (fread((char*)contents.data(), file_size, 1, file) != 1) {
+      status =
+          iree_make_status(iree_status_code_from_errno(errno),
+                           "unable to read entire file contents of '%s'", path);
+    }
+  }
+  if (iree_status_is_ok(status)) {
+    *out_contents = std::move(contents);
+  }
+  fclose(file);
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+iree_status_t SetFileContents(const char* path,
+                              iree_const_byte_span_t content) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  FILE* file = fopen(path, "wb");
+  if (file == NULL) {
+    IREE_TRACE_ZONE_END(z0);
+    return iree_make_status(iree_status_code_from_errno(errno),
+                            "failed to open file '%s'", path);
+  }
+  int ret = fwrite((char*)content.data, content.data_length, 1, file);
+  iree_status_t status = iree_ok_status();
+  if (ret != 1) {
+    status =
+        iree_make_status(IREE_STATUS_DATA_LOSS,
+                         "unable to write entire file contents of '%s'", path);
+  }
+  fclose(file);
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+}  // namespace file_io
+}  // namespace iree
diff --git a/iree/base/internal/file_io.h b/iree/base/internal/file_io.h
index 00d53df..144204c 100644
--- a/iree/base/internal/file_io.h
+++ b/iree/base/internal/file_io.h
@@ -17,8 +17,7 @@
 
 #include <string>
 
-#include "absl/strings/string_view.h"
-#include "iree/base/status.h"
+#include "iree/base/api.h"
 
 namespace iree {
 namespace file_io {
@@ -27,33 +26,13 @@
 //
 // Returns an OK status if the file definitely exists.
 // Errors can include PermissionDeniedError, NotFoundError, etc.
-Status FileExists(const std::string& path);
+iree_status_t FileExists(const char* path);
 
 // Synchronously reads a file's contents into a string.
-Status GetFileContents(const std::string& path, std::string* out_contents);
+iree_status_t GetFileContents(const char* path, std::string* out_contents);
 
 // Synchronously writes a string into a file, overwriting its contents.
-Status SetFileContents(const std::string& path, absl::string_view content);
-
-// Deletes the file at the provided path.
-Status DeleteFile(const std::string& path);
-
-// Moves a file from 'source_path' to 'destination_path'.
-//
-// This may simply rename the file, but may fall back to a full copy and delete
-// of the original if renaming is not possible (for example when moving between
-// physical storage locations).
-Status MoveFile(const std::string& source_path,
-                const std::string& destination_path);
-
-// Gets a platform and environment-dependent path for temporary files.
-std::string GetTempPath();
-
-// TODO(#3845): remove this when dylibs no longer need temp files.
-// Gets a temporary file name and returns its absolute path.
-// The particular path chosen is platform and environment-dependent.
-// Unique characters will be automatically inserted after |base_name|.
-Status GetTempFile(absl::string_view base_name, std::string* out_path);
+iree_status_t SetFileContents(const char* path, iree_const_byte_span_t content);
 
 }  // namespace file_io
 }  // namespace iree
diff --git a/iree/base/internal/file_io_posix.cc b/iree/base/internal/file_io_posix.cc
deleted file mode 100644
index 81a04ce..0000000
--- a/iree/base/internal/file_io_posix.cc
+++ /dev/null
@@ -1,161 +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/base/target_platform.h"
-
-#if defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_APPLE) || \
-    defined(IREE_PLATFORM_LINUX)
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <cstdio>
-#include <cstdlib>
-
-#include "absl/strings/str_cat.h"
-#include "iree/base/internal/file_io.h"
-#include "iree/base/status.h"
-#include "iree/base/tracing.h"
-
-namespace iree {
-namespace file_io {
-
-Status FileExists(const std::string& path) {
-  IREE_TRACE_SCOPE0("file_io::FileExists");
-  struct stat stat_buf;
-  return stat(path.c_str(), &stat_buf) == 0
-             ? iree_ok_status()
-             : iree_make_status(IREE_STATUS_NOT_FOUND, "'%s'", path.c_str());
-}
-
-Status GetFileContents(const std::string& path, std::string* out_contents) {
-  IREE_TRACE_SCOPE0("file_io::GetFileContents");
-  *out_contents = std::string();
-  std::unique_ptr<FILE, void (*)(FILE*)> file = {std::fopen(path.c_str(), "r"),
-                                                 +[](FILE* file) {
-                                                   if (file) fclose(file);
-                                                 }};
-  if (file == nullptr) {
-    return iree_make_status(iree_status_code_from_errno(errno),
-                            "failed to open file '%s'", path.c_str());
-  }
-  if (std::fseek(file.get(), 0, SEEK_END) == -1) {
-    return iree_make_status(iree_status_code_from_errno(errno), "seek (end)");
-  }
-  size_t file_size = std::ftell(file.get());
-  if (file_size == -1L) {
-    return iree_make_status(iree_status_code_from_errno(errno), "size query");
-  }
-  if (std::fseek(file.get(), 0, SEEK_SET) == -1) {
-    return iree_make_status(iree_status_code_from_errno(errno), "seek (beg)");
-  }
-  std::string contents;
-  contents.resize(file_size);
-  if (std::fread(const_cast<char*>(contents.data()), file_size, 1,
-                 file.get()) != 1) {
-    return iree_make_status(IREE_STATUS_UNAVAILABLE,
-                            "unable to read entire file contents of '%.*s'",
-                            (int)path.size(), path.data());
-  }
-  *out_contents = std::move(contents);
-  return OkStatus();
-}
-
-Status SetFileContents(const std::string& path, absl::string_view content) {
-  IREE_TRACE_SCOPE0("file_io::SetFileContents");
-  std::unique_ptr<FILE, void (*)(FILE*)> file = {std::fopen(path.c_str(), "wb"),
-                                                 +[](FILE* file) {
-                                                   if (file) fclose(file);
-                                                 }};
-  if (file == nullptr) {
-    return iree_make_status(iree_status_code_from_errno(errno),
-                            "failed to open file '%s'", path.c_str());
-  }
-  if (std::fwrite(const_cast<char*>(content.data()), content.size(), 1,
-                  file.get()) != 1) {
-    return iree_make_status(IREE_STATUS_UNAVAILABLE,
-                            "unable to write entire file contents of '%.*s'",
-                            (int)path.size(), path.data());
-  }
-  return OkStatus();
-}
-
-Status DeleteFile(const std::string& path) {
-  IREE_TRACE_SCOPE0("file_io::DeleteFile");
-  if (::remove(path.c_str()) == -1) {
-    return iree_make_status(iree_status_code_from_errno(errno),
-                            "failed to delete file '%s'", path.c_str());
-  }
-  return OkStatus();
-}
-
-Status MoveFile(const std::string& source_path,
-                const std::string& destination_path) {
-  IREE_TRACE_SCOPE0("file_io::MoveFile");
-  if (::rename(source_path.c_str(), destination_path.c_str()) == -1) {
-    return iree_make_status(iree_status_code_from_errno(errno),
-                            "failed to rename file '%s' to '%s'",
-                            source_path.c_str(), destination_path.c_str());
-  }
-  return OkStatus();
-}
-
-std::string GetTempPath() {
-  IREE_TRACE_SCOPE0("file_io::GetTempPath");
-
-  // TEST_TMPDIR will point to a writeable temp path when running bazel tests.
-  char* test_tmpdir = getenv("TEST_TMPDIR");
-  if (test_tmpdir) {
-    return test_tmpdir;
-  }
-
-  char* tmpdir = getenv("TMPDIR");
-  if (tmpdir) {
-    return tmpdir;
-  }
-
-#ifdef __ANDROID__
-  // Support running Android command-line programs both as regular shell user
-  // and as root. For the latter, TMPDIR is not defined by default.
-  return "/data/local/tmp";
-#else
-  return "/tmp";
-#endif
-}
-
-// TODO(#3845): remove this when dylibs no longer need temp files.
-Status GetTempFile(absl::string_view base_name, std::string* out_path) {
-  IREE_TRACE_SCOPE0("file_io::GetTempFile");
-  *out_path = std::string();
-
-  std::string temp_path = GetTempPath();
-  std::string template_path =
-      temp_path + "/" + std::string(base_name) + "XXXXXX";
-
-  if (::mkstemp(&template_path[0]) != -1) {
-    // Should have been modified by mkstemp.
-    *out_path = std::move(template_path);
-    return OkStatus();
-  } else {
-    return iree_make_status(iree_status_code_from_errno(errno),
-                            "failed to create temp file with template '%s'",
-                            template_path.c_str());
-  }
-}
-
-}  // namespace file_io
-}  // namespace iree
-
-#endif  // IREE_PLATFORM_*
diff --git a/iree/base/internal/file_io_test.cc b/iree/base/internal/file_io_test.cc
index 37dc1a1..e628fa9 100644
--- a/iree/base/internal/file_io_test.cc
+++ b/iree/base/internal/file_io_test.cc
@@ -14,10 +14,6 @@
 
 #include "iree/base/internal/file_io.h"
 
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
-#include "iree/base/logging.h"
-#include "iree/base/status.h"
 #include "iree/testing/gtest.h"
 #include "iree/testing/status_matchers.h"
 
@@ -27,82 +23,36 @@
 
 using ::iree::testing::status::StatusIs;
 
-std::string GetUniquePath(absl::string_view unique_name) {
+std::string GetUniquePath(const char* unique_name) {
   char* test_tmpdir = getenv("TEST_TMPDIR");
-  IREE_CHECK(test_tmpdir) << "TEST_TMPDIR not defined";
-  return test_tmpdir + std::string("/") + std::string(unique_name) +
-         "_test.txt";
+  if (!test_tmpdir) {
+    test_tmpdir = getenv("TMPDIR");
+  }
+  if (!test_tmpdir) {
+    test_tmpdir = getenv("TEMP");
+  }
+  IREE_CHECK(test_tmpdir) << "TEST_TMPDIR/TMPDIR/TEMP not defined";
+  return test_tmpdir + std::string("/iree_test_") + unique_name;
 }
 
-std::string GetUniqueContents(absl::string_view unique_name) {
-  return absl::StrCat("Test with name ", unique_name, "\n");
+std::string GetUniqueContents(const char* unique_name) {
+  return std::string("Test with name ") + unique_name + "\n";
 }
 
 TEST(FileIo, GetSetContents) {
-  std::string unique_name = "GetSetContents";
-  auto path = GetUniquePath(unique_name);
-  ASSERT_THAT(FileExists(path), StatusIs(StatusCode::kNotFound));
-  auto to_write = GetUniqueContents(unique_name);
+  constexpr const char* kUniqueName = "GetSetContents";
+  auto path = GetUniquePath(kUniqueName);
+  ASSERT_THAT(FileExists(path.c_str()), StatusIs(StatusCode::kNotFound));
+  auto to_write = GetUniqueContents(kUniqueName);
 
-  IREE_ASSERT_OK(SetFileContents(path, to_write));
+  IREE_ASSERT_OK(SetFileContents(
+      path.c_str(),
+      iree_make_const_byte_span(to_write.data(), to_write.size())));
   std::string read;
-  IREE_ASSERT_OK(GetFileContents(path, &read));
+  IREE_ASSERT_OK(GetFileContents(path.c_str(), &read));
   EXPECT_EQ(to_write, read);
 }
 
-TEST(FileIo, SetDeleteExists) {
-  std::string unique_name = "SetDeleteExists";
-  auto path = GetUniquePath(unique_name);
-  ASSERT_THAT(FileExists(path), StatusIs(StatusCode::kNotFound));
-  auto to_write = GetUniqueContents(unique_name);
-
-  IREE_ASSERT_OK(SetFileContents(path, to_write));
-  IREE_ASSERT_OK(FileExists(path));
-  IREE_ASSERT_OK(DeleteFile(path));
-  EXPECT_THAT(FileExists(path), StatusIs(StatusCode::kNotFound));
-}
-
-TEST(FileIo, MoveFile) {
-  auto from_path = GetUniquePath("MoveFileFrom");
-  auto to_path = GetUniquePath("MoveFileTo");
-  ASSERT_THAT(FileExists(from_path), StatusIs(StatusCode::kNotFound));
-  ASSERT_THAT(FileExists(to_path), StatusIs(StatusCode::kNotFound));
-  auto to_write = GetUniqueContents("MoveFile");
-
-  IREE_ASSERT_OK(SetFileContents(from_path, to_write));
-  IREE_ASSERT_OK(FileExists(from_path));
-  IREE_EXPECT_OK(MoveFile(from_path, to_path));
-  EXPECT_THAT(FileExists(from_path), StatusIs(StatusCode::kNotFound));
-  IREE_EXPECT_OK(FileExists(to_path));
-  std::string read;
-  IREE_ASSERT_OK(GetFileContents(to_path, &read));
-  EXPECT_EQ(to_write, read);
-}
-
-TEST(FileIo, GetTempPath) {
-  auto temp_path = GetTempPath();
-  EXPECT_NE("", temp_path);
-}
-
-TEST(FileIo, GetTempFile) {
-  std::string path1;
-  IREE_ASSERT_OK(GetTempFile("foo", &path1));
-  EXPECT_TRUE(path1.find("foo") != std::string::npos);
-
-  // Should be able to set file contents at the given path.
-  // Note that the file may or may not exist, depending on the platform, and
-  // a file must be created at the path before calling GetTempFile again, or
-  // else the same path may be returned.
-  auto to_write = GetUniqueContents("GetTempFile");
-  IREE_ASSERT_OK(SetFileContents(path1, to_write));
-
-  // Create another temp file with the same base name, check for a unique path.
-  std::string path2;
-  IREE_ASSERT_OK(GetTempFile("foo", &path2));
-  EXPECT_TRUE(path2.find("foo") != std::string::npos);
-  EXPECT_NE(path1, path2);
-}
-
 }  // namespace
 }  // namespace file_io
 }  // namespace iree
diff --git a/iree/base/internal/file_io_win32.cc b/iree/base/internal/file_io_win32.cc
deleted file mode 100644
index 2a7cc7b..0000000
--- a/iree/base/internal/file_io_win32.cc
+++ /dev/null
@@ -1,145 +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/base/target_platform.h"
-
-#if defined(IREE_PLATFORM_WINDOWS)
-
-#include <io.h>
-
-#include <atomic>
-
-#include "absl/memory/memory.h"
-#include "absl/strings/str_cat.h"
-#include "iree/base/internal/file_handle_win32.h"
-#include "iree/base/internal/file_io.h"
-#include "iree/base/target_platform.h"
-#include "iree/base/tracing.h"
-
-namespace iree {
-namespace file_io {
-
-Status FileExists(const std::string& path) {
-  IREE_TRACE_SCOPE0("file_io::FileExists");
-  DWORD attrs = ::GetFileAttributesA(path.c_str());
-  if (attrs == INVALID_FILE_ATTRIBUTES) {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to find/access file: %s", path.c_str());
-  }
-  return OkStatus();
-}
-
-Status GetFileContents(const std::string& path, std::string* out_contents) {
-  IREE_TRACE_SCOPE0("file_io::GetFileContents");
-  *out_contents = std::string();
-  std::unique_ptr<FileHandle> file;
-  IREE_RETURN_IF_ERROR(
-      FileHandle::OpenRead(std::move(path), FILE_FLAG_SEQUENTIAL_SCAN, &file));
-  std::string contents;
-  contents.resize(file->size());
-  DWORD bytes_read = 0;
-  if (::ReadFile(file->handle(), const_cast<char*>(contents.data()),
-                 static_cast<DWORD>(contents.size()), &bytes_read,
-                 nullptr) == FALSE) {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to read file span of %zu bytes from '%s'",
-                            contents.size(), path.c_str());
-  } else if (bytes_read != file->size()) {
-    return iree_make_status(
-        IREE_STATUS_RESOURCE_EXHAUSTED,
-        "unable to read all %zu bytes from '%.*s' (got %zu)", file->size(),
-        (int)path.size(), path.data(), bytes_read);
-  }
-  *out_contents = contents;
-  return OkStatus();
-}
-
-Status SetFileContents(const std::string& path, absl::string_view content) {
-  IREE_TRACE_SCOPE0("file_io::SetFileContents");
-  std::unique_ptr<FileHandle> file;
-  IREE_RETURN_IF_ERROR(FileHandle::OpenWrite(std::move(path), 0, &file));
-  if (::WriteFile(file->handle(), content.data(),
-                  static_cast<DWORD>(content.size()), NULL, NULL) == FALSE) {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to write file span of %zu bytes to '%s'",
-                            content.size(), path.c_str());
-  }
-  return OkStatus();
-}
-
-Status DeleteFile(const std::string& path) {
-  IREE_TRACE_SCOPE0("file_io::DeleteFile");
-  if (::DeleteFileA(path.c_str()) == FALSE) {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to delete/access file: %s", path.c_str());
-  }
-  return OkStatus();
-}
-
-Status MoveFile(const std::string& source_path,
-                const std::string& destination_path) {
-  IREE_TRACE_SCOPE0("file_io::MoveFile");
-  if (::MoveFileA(source_path.c_str(), destination_path.c_str()) == FALSE) {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to move file '%s' to '%s'",
-                            source_path.c_str(), destination_path.c_str());
-  }
-  return OkStatus();
-}
-
-std::string GetTempPath() {
-  IREE_TRACE_SCOPE0("file_io::GetTempPath");
-
-  // TEST_TMPDIR will point to a writeable temp path when running bazel tests.
-  char* test_tmpdir = getenv("TEST_TMPDIR");
-  if (test_tmpdir) {
-    return test_tmpdir;
-  }
-
-  std::string temp_path(64, '\0');
-  for (bool retry_query = true; retry_query;) {
-    DWORD required_length =
-        ::GetTempPathA(static_cast<DWORD>(temp_path.size()), &temp_path[0]);
-    retry_query = required_length > temp_path.size();
-    temp_path.resize(required_length);
-  }
-  return temp_path;
-}
-
-// TODO(#3845): remove this when dylibs no longer need temp files.
-Status GetTempFile(absl::string_view base_name, std::string* out_path) {
-  IREE_TRACE_SCOPE0("file_io::GetTempFile");
-  *out_path = std::string();
-
-  std::string temp_path = GetTempPath();
-  std::string template_path =
-      temp_path + std::string("\\") + std::string(base_name) + "XXXXXX";
-
-  if (::_mktemp(&template_path[0]) != nullptr) {
-    // Should have been modified by _mktemp.
-    static std::atomic<int> next_id{0};
-    template_path += std::to_string(next_id++);
-    *out_path = std::move(template_path);
-    return OkStatus();
-  } else {
-    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
-                            "unable to create temp file with template '%s'",
-                            template_path.c_str());
-  }
-}
-
-}  // namespace file_io
-}  // namespace iree
-
-#endif  // IREE_PLATFORM_WINDOWS
diff --git a/iree/base/testing/BUILD b/iree/base/testing/BUILD
index 9a897f1..299a3d1 100644
--- a/iree/base/testing/BUILD
+++ b/iree/base/testing/BUILD
@@ -43,8 +43,7 @@
     deps = [
         ":dynamic_library_test_library",
         "//iree/base:core_headers",
-        "//iree/base:dynamic_library",
-        "//iree/base:status",
+        "//iree/base/internal:dynamic_library",
         "//iree/base/internal:file_io",
         "//iree/testing:gtest",
         "//iree/testing:gtest_main",
diff --git a/iree/base/testing/CMakeLists.txt b/iree/base/testing/CMakeLists.txt
index 8b0c957..6c3a4a2 100644
--- a/iree/base/testing/CMakeLists.txt
+++ b/iree/base/testing/CMakeLists.txt
@@ -50,9 +50,8 @@
   DEPS
     ::dynamic_library_test_library
     iree::base::core_headers
-    iree::base::dynamic_library
+    iree::base::internal::dynamic_library
     iree::base::internal::file_io
-    iree::base::status
     iree::testing::gtest
     iree::testing::gtest_main
 )
diff --git a/iree/base/testing/dynamic_library_test.cc b/iree/base/testing/dynamic_library_test.cc
index 0f831ba..559950b 100644
--- a/iree/base/testing/dynamic_library_test.cc
+++ b/iree/base/testing/dynamic_library_test.cc
@@ -12,12 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "iree/base/dynamic_library.h"
+#include "iree/base/internal/dynamic_library.h"
 
 #include <string>
 
 #include "iree/base/internal/file_io.h"
-#include "iree/base/status.h"
 #include "iree/base/target_platform.h"
 #include "iree/base/testing/dynamic_library_test_library_embed.h"
 #include "iree/testing/gtest.h"
@@ -32,6 +31,20 @@
 
 class DynamicLibraryTest : public ::testing::Test {
  public:
+  static std::string GetTempFilename(const char* suffix) {
+    static int unique_id = 0;
+    char* test_tmpdir = getenv("TEST_TMPDIR");
+    if (!test_tmpdir) {
+      test_tmpdir = getenv("TMPDIR");
+    }
+    if (!test_tmpdir) {
+      test_tmpdir = getenv("TEMP");
+    }
+    IREE_CHECK(test_tmpdir) << "TEST_TMPDIR/TMPDIR/TEMP not defined";
+    return test_tmpdir + std::string("/iree_test_") +
+           std::to_string(unique_id++) + suffix;
+  }
+
   static void SetUpTestCase() {
     // Making files available to tests, particularly across operating systems
     // and build tools (Bazel/CMake) is complicated. Rather than include a test
@@ -39,19 +52,18 @@
     // the file so it's embedded in a C++ module, then write that embedded file
     // to a platform/test-environment specific temp file for loading.
 
-    std::string base_name = "dynamic_library_test_library";
-    IREE_ASSERT_OK(file_io::GetTempFile(base_name, &library_temp_path_));
     // System APIs for loading dynamic libraries typically require an extension.
 #if defined(IREE_PLATFORM_WINDOWS)
-    library_temp_path_ += ".dll";
+    static constexpr const char* ext = ".dll";
 #else
-    library_temp_path_ += ".so";
+    static constexpr const char* ext = ".so";
 #endif
+    library_temp_path_ = GetTempFilename(ext);
 
     const auto* file_toc = dynamic_library_test_library_create();
-    absl::string_view file_data(reinterpret_cast<const char*>(file_toc->data),
-                                file_toc->size);
-    IREE_ASSERT_OK(file_io::SetFileContents(library_temp_path_, file_data));
+    IREE_ASSERT_OK(file_io::SetFileContents(
+        library_temp_path_.c_str(),
+        iree_make_const_byte_span(file_toc->data, file_toc->size)));
 
     IREE_LOG(INFO) << "Embedded test library written to temp path: "
                    << library_temp_path_;
@@ -63,38 +75,62 @@
 std::string DynamicLibraryTest::library_temp_path_;
 
 TEST_F(DynamicLibraryTest, LoadLibrarySuccess) {
-  std::unique_ptr<DynamicLibrary> library;
-  IREE_ASSERT_OK(DynamicLibrary::Load(library_temp_path_.c_str(), &library));
+  iree_dynamic_library_t* library = NULL;
+  IREE_ASSERT_OK(iree_dynamic_library_load_from_file(
+      library_temp_path_.c_str(), IREE_DYNAMIC_LIBRARY_FLAG_NONE,
+      iree_allocator_system(), &library));
+  iree_dynamic_library_release(library);
 }
 
 TEST_F(DynamicLibraryTest, LoadLibraryFailure) {
-  std::unique_ptr<DynamicLibrary> library;
-  EXPECT_THAT(DynamicLibrary::Load(kUnknownName, &library),
-              StatusIs(iree::StatusCode::kUnavailable));
+  iree_dynamic_library_t* library = NULL;
+  EXPECT_THAT(iree_dynamic_library_load_from_file(
+                  kUnknownName, IREE_DYNAMIC_LIBRARY_FLAG_NONE,
+                  iree_allocator_system(), &library),
+              StatusIs(iree::StatusCode::kNotFound));
 }
 
 TEST_F(DynamicLibraryTest, LoadLibraryTwice) {
-  std::unique_ptr<DynamicLibrary> library1;
-  IREE_ASSERT_OK(DynamicLibrary::Load(library_temp_path_.c_str(), &library1));
-  std::unique_ptr<DynamicLibrary> library2;
-  IREE_ASSERT_OK(DynamicLibrary::Load(library_temp_path_.c_str(), &library2));
+  iree_dynamic_library_t* library1 = NULL;
+  iree_dynamic_library_t* library2 = NULL;
+  IREE_ASSERT_OK(iree_dynamic_library_load_from_file(
+      library_temp_path_.c_str(), IREE_DYNAMIC_LIBRARY_FLAG_NONE,
+      iree_allocator_system(), &library1));
+  IREE_ASSERT_OK(iree_dynamic_library_load_from_file(
+      library_temp_path_.c_str(), IREE_DYNAMIC_LIBRARY_FLAG_NONE,
+      iree_allocator_system(), &library2));
+  iree_dynamic_library_release(library1);
+  iree_dynamic_library_release(library2);
 }
 
 TEST_F(DynamicLibraryTest, GetSymbolSuccess) {
-  std::unique_ptr<DynamicLibrary> library;
-  IREE_ASSERT_OK(DynamicLibrary::Load(library_temp_path_.c_str(), &library));
+  iree_dynamic_library_t* library = NULL;
+  IREE_ASSERT_OK(iree_dynamic_library_load_from_file(
+      library_temp_path_.c_str(), IREE_DYNAMIC_LIBRARY_FLAG_NONE,
+      iree_allocator_system(), &library));
 
-  auto times_two_fn = library->GetSymbol<int (*)(int)>("times_two");
-  ASSERT_NE(nullptr, times_two_fn);
-  EXPECT_EQ(246, times_two_fn(123));
+  int (*fn_ptr)(int);
+  IREE_ASSERT_OK(iree_dynamic_library_lookup_symbol(library, "times_two",
+                                                    (void**)&fn_ptr));
+  ASSERT_NE(nullptr, fn_ptr);
+  EXPECT_EQ(246, fn_ptr(123));
+
+  iree_dynamic_library_release(library);
 }
 
 TEST_F(DynamicLibraryTest, GetSymbolFailure) {
-  std::unique_ptr<DynamicLibrary> library;
-  IREE_ASSERT_OK(DynamicLibrary::Load(library_temp_path_.c_str(), &library));
+  iree_dynamic_library_t* library = NULL;
+  IREE_ASSERT_OK(iree_dynamic_library_load_from_file(
+      library_temp_path_.c_str(), IREE_DYNAMIC_LIBRARY_FLAG_NONE,
+      iree_allocator_system(), &library));
 
-  auto unknown_fn = library->GetSymbol<int (*)(int)>("unknown");
-  EXPECT_EQ(nullptr, unknown_fn);
+  int (*fn_ptr)(int);
+  EXPECT_THAT(
+      iree_dynamic_library_lookup_symbol(library, "unknown", (void**)&fn_ptr),
+      StatusIs(iree::StatusCode::kNotFound));
+  EXPECT_EQ(nullptr, fn_ptr);
+
+  iree_dynamic_library_release(library);
 }
 
 }  // namespace
diff --git a/iree/base/testing/dynamic_library_test_library.cc b/iree/base/testing/dynamic_library_test_library.cc
index 4d99552..20cde18 100644
--- a/iree/base/testing/dynamic_library_test_library.cc
+++ b/iree/base/testing/dynamic_library_test_library.cc
@@ -13,9 +13,7 @@
 // limitations under the License.
 
 #ifdef __cplusplus
-#define IREE_API_EXPORT extern "C"
-#else
-#define IREE_API_EXPORT
+extern "C" {
 #endif  // __cplusplus
 
 #if defined(_WIN32)
@@ -24,4 +22,8 @@
 #define IREE_SYM_EXPORT __attribute__((visibility("default")))
 #endif  // _WIN32
 
-IREE_API_EXPORT int IREE_SYM_EXPORT times_two(int value) { return value * 2; }
+int IREE_SYM_EXPORT times_two(int value) { return value * 2; }
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
diff --git a/iree/hal/cuda/BUILD b/iree/hal/cuda/BUILD
index b1c1d88..00f80ef 100644
--- a/iree/hal/cuda/BUILD
+++ b/iree/hal/cuda/BUILD
@@ -80,18 +80,18 @@
     name = "dynamic_symbols",
     srcs = [
         "cuda_headers.h",
-        "dynamic_symbols.cc",
-        "dynamic_symbols_tables.h",
+        "dynamic_symbols.c",
     ],
     hdrs = [
         "dynamic_symbols.h",
     ],
+    textual_hdrs = [
+        "dynamic_symbol_tables.h",
+    ],
     deps = [
         "//iree/base:core_headers",
-        "//iree/base:dynamic_library",
-        "//iree/base:status",
         "//iree/base:tracing",
-        "@com_google_absl//absl/types:span",
+        "//iree/base/internal:dynamic_library",
         "@cuda_headers",
     ],
 )
diff --git a/iree/hal/cuda/CMakeLists.txt b/iree/hal/cuda/CMakeLists.txt
index 205e343..967bde4 100644
--- a/iree/hal/cuda/CMakeLists.txt
+++ b/iree/hal/cuda/CMakeLists.txt
@@ -65,16 +65,15 @@
     dynamic_symbols
   HDRS
     "dynamic_symbols.h"
+  TEXTUAL_HDRS
+    "dynamic_symbol_tables.h"
   SRCS
     "cuda_headers.h"
-    "dynamic_symbols.cc"
-    "dynamic_symbols_tables.h"
+    "dynamic_symbols.c"
   DEPS
-    absl::span
     cuda_headers
     iree::base::core_headers
-    iree::base::dynamic_library
-    iree::base::status
+    iree::base::internal::dynamic_library
     iree::base::tracing
   PUBLIC
 )
diff --git a/iree/hal/cuda/cuda_driver.c b/iree/hal/cuda/cuda_driver.c
index 1f83363..1a1b20f 100644
--- a/iree/hal/cuda/cuda_driver.c
+++ b/iree/hal/cuda/cuda_driver.c
@@ -61,7 +61,8 @@
       identifier, &driver->identifier,
       (char*)driver + total_size - identifier.size);
   driver->default_device_index = options->default_device_index;
-  iree_status_t status = load_symbols(&driver->syms);
+  iree_status_t status =
+      iree_hal_cuda_dynamic_symbols_initialize(host_allocator, &driver->syms);
   if (iree_status_is_ok(status)) {
     *out_driver = (iree_hal_driver_t*)driver;
   } else {
@@ -75,7 +76,7 @@
   iree_allocator_t host_allocator = driver->host_allocator;
   IREE_TRACE_ZONE_BEGIN(z0);
 
-  unload_symbols(&driver->syms);
+  iree_hal_cuda_dynamic_symbols_deinitialize(&driver->syms);
   iree_allocator_free(host_allocator, driver);
 
   IREE_TRACE_ZONE_END(z0);
diff --git a/iree/hal/cuda/dynamic_symbols_tables.h b/iree/hal/cuda/dynamic_symbol_tables.h
similarity index 100%
rename from iree/hal/cuda/dynamic_symbols_tables.h
rename to iree/hal/cuda/dynamic_symbol_tables.h
diff --git a/iree/hal/cuda/dynamic_symbols.c b/iree/hal/cuda/dynamic_symbols.c
new file mode 100644
index 0000000..2449006
--- /dev/null
+++ b/iree/hal/cuda/dynamic_symbols.c
@@ -0,0 +1,73 @@
+// Copyright 2021 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/cuda/dynamic_symbols.h"
+
+#include <stddef.h>
+
+#include "iree/base/internal/dynamic_library.h"
+#include "iree/base/target_platform.h"
+#include "iree/base/tracing.h"
+
+static const char* kCUDALoaderSearchNames[] = {
+#if defined(IREE_PLATFORM_WINDOWS)
+    "nvcuda.dll",
+#else
+    "libcuda.so",
+#endif
+};
+
+static iree_status_t iree_hal_cuda_dynamic_symbols_resolve_all(
+    iree_hal_cuda_dynamic_symbols_t* syms) {
+#define CU_PFN_DECL(cudaSymbolName, ...)                              \
+  {                                                                   \
+    static const char* kName = #cudaSymbolName;                       \
+    IREE_RETURN_IF_ERROR(iree_dynamic_library_lookup_symbol(          \
+        syms->loader_library, kName, (void**)&syms->cudaSymbolName)); \
+  }
+#include "iree/hal/cuda/dynamic_symbol_tables.h"
+#undef CU_PFN_DECL
+  return iree_ok_status();
+}
+
+iree_status_t iree_hal_cuda_dynamic_symbols_initialize(
+    iree_allocator_t allocator, iree_hal_cuda_dynamic_symbols_t* out_syms) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  memset(out_syms, 0, sizeof(*out_syms));
+  iree_status_t status = iree_dynamic_library_load_from_files(
+      IREE_ARRAYSIZE(kCUDALoaderSearchNames), kCUDALoaderSearchNames,
+      IREE_DYNAMIC_LIBRARY_FLAG_NONE, allocator, &out_syms->loader_library);
+  if (iree_status_is_not_found(status)) {
+    iree_status_ignore(status);
+    return iree_make_status(
+        IREE_STATUS_UNAVAILABLE,
+        "CUDA runtime library not available; ensure installed and on path");
+  }
+  if (iree_status_is_ok(status)) {
+    status = iree_hal_cuda_dynamic_symbols_resolve_all(out_syms);
+  }
+  if (!iree_status_is_ok(status)) {
+    iree_hal_cuda_dynamic_symbols_deinitialize(out_syms);
+  }
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+void iree_hal_cuda_dynamic_symbols_deinitialize(
+    iree_hal_cuda_dynamic_symbols_t* syms) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  iree_dynamic_library_release(syms->loader_library);
+  memset(syms, 0, sizeof(*syms));
+  IREE_TRACE_ZONE_END(z0);
+}
diff --git a/iree/hal/cuda/dynamic_symbols.cc b/iree/hal/cuda/dynamic_symbols.cc
deleted file mode 100644
index df13a4d..0000000
--- a/iree/hal/cuda/dynamic_symbols.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2021 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/cuda/dynamic_symbols.h"
-
-#include <cstddef>
-
-#include "absl/types/span.h"
-#include "iree/base/dynamic_library.h"
-#include "iree/base/status.h"
-#include "iree/base/target_platform.h"
-#include "iree/base/tracing.h"
-
-static const char* kCUDALoaderSearchNames[] = {
-#if defined(IREE_PLATFORM_WINDOWS)
-    "nvcuda.dll",
-#else
-    "libcuda.so",
-#endif
-};
-
-extern "C" {
-
-iree_status_t load_symbols(iree_hal_cuda_dynamic_symbols_t* syms) {
-  std::unique_ptr<iree::DynamicLibrary> loader_library;
-  IREE_RETURN_IF_ERROR(iree::DynamicLibrary::Load(
-      absl::MakeSpan(kCUDALoaderSearchNames), &loader_library));
-
-#define CU_PFN_DECL(cudaSymbolName, ...)                                    \
-  {                                                                         \
-    using FuncPtrT = decltype(syms->cudaSymbolName);                        \
-    static const char* kName = #cudaSymbolName;                             \
-    syms->cudaSymbolName = loader_library->GetSymbol<FuncPtrT>(kName);      \
-    if (!syms->cudaSymbolName) {                                            \
-      return iree_make_status(IREE_STATUS_UNAVAILABLE, "symbol not found"); \
-    }                                                                       \
-  }
-
-#include "dynamic_symbols_tables.h"
-#undef CU_PFN_DECL
-  syms->opaque_loader_library_ = (void*)loader_library.release();
-  return iree_ok_status();
-}
-
-void unload_symbols(iree_hal_cuda_dynamic_symbols_t* syms) {
-  delete (iree::DynamicLibrary*)syms->opaque_loader_library_;
-}
-
-}  // extern "C"
\ No newline at end of file
diff --git a/iree/hal/cuda/dynamic_symbols.h b/iree/hal/cuda/dynamic_symbols.h
index 436b32d..97d9ba1 100644
--- a/iree/hal/cuda/dynamic_symbols.h
+++ b/iree/hal/cuda/dynamic_symbols.h
@@ -16,25 +16,37 @@
 #define IREE_HAL_CUDA_DYNAMIC_SYMBOLS_H_
 
 #include "iree/base/api.h"
+#include "iree/base/internal/dynamic_library.h"
 #include "iree/hal/cuda/cuda_headers.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif  // __cplusplus
-/// DyanmicSymbols allow loading dynamically a subset of CUDA driver API. It
-/// loads all the function declared in `dynamic_symbol_tables.def` and fail if
-/// any of the symbol is not available. The functions signatures are matching
-/// the declarations in `cuda.h`.
+
+// DynamicSymbols allow loading dynamically a subset of CUDA driver API. It
+// loads all the function declared in `dynamic_symbol_tables.def` and fail if
+// any of the symbol is not available. The functions signatures are matching
+// the declarations in `cuda.h`.
 typedef struct {
+  iree_dynamic_library_t* loader_library;
+
 #define CU_PFN_DECL(cudaSymbolName, ...) \
   CUresult (*cudaSymbolName)(__VA_ARGS__);
-#include "dynamic_symbols_tables.h"
+#include "iree/hal/cuda/dynamic_symbol_tables.h"
 #undef CU_PFN_DECL
-  void* opaque_loader_library_;
 } iree_hal_cuda_dynamic_symbols_t;
 
-iree_status_t load_symbols(iree_hal_cuda_dynamic_symbols_t* syms);
-void unload_symbols(iree_hal_cuda_dynamic_symbols_t* syms);
+// Initializes |out_syms| in-place with dynamically loaded CUDA symbols.
+// iree_hal_cuda_dynamic_symbols_deinitialize must be used to release the
+// library resources.
+iree_status_t iree_hal_cuda_dynamic_symbols_initialize(
+    iree_allocator_t allocator, iree_hal_cuda_dynamic_symbols_t* out_syms);
+
+// Deinitializes |syms| by unloading the backing library. All function pointers
+// will be invalidated. They _may_ still work if there are other reasons the
+// library remains loaded so be careful.
+void iree_hal_cuda_dynamic_symbols_deinitialize(
+    iree_hal_cuda_dynamic_symbols_t* syms);
 
 #ifdef __cplusplus
 }  // extern "C"
diff --git a/iree/hal/cuda/dynamic_symbols_test.cc b/iree/hal/cuda/dynamic_symbols_test.cc
index 5b8681f..566c060 100644
--- a/iree/hal/cuda/dynamic_symbols_test.cc
+++ b/iree/hal/cuda/dynamic_symbols_test.cc
@@ -30,7 +30,8 @@
 
 TEST(DynamicSymbolsTest, CreateFromSystemLoader) {
   iree_hal_cuda_dynamic_symbols_t symbols;
-  iree_status_t status = load_symbols(&symbols);
+  iree_status_t status = iree_hal_cuda_dynamic_symbols_initialize(
+      iree_allocator_system(), &symbols);
   if (!iree_status_is_ok(status)) {
     IREE_LOG(WARNING) << "Symbols cannot be loaded, skipping test.";
     GTEST_SKIP();
@@ -43,7 +44,8 @@
     CUdevice device;
     CUDE_CHECK_ERRORS(symbols.cuDeviceGet(&device, /*ordinal=*/0));
   }
-  unload_symbols(&symbols);
+
+  iree_hal_cuda_dynamic_symbols_deinitialize(&symbols);
 }
 
 }  // namespace
diff --git a/iree/hal/local/loaders/BUILD b/iree/hal/local/loaders/BUILD
index 8c711c3..618881d 100644
--- a/iree/hal/local/loaders/BUILD
+++ b/iree/hal/local/loaders/BUILD
@@ -25,7 +25,7 @@
 
 cc_library(
     name = "legacy_library_loader",
-    srcs = ["legacy_library_loader.cc"],
+    srcs = ["legacy_library_loader.c"],
     hdrs = ["legacy_library_loader.h"],
     defines = [
         "IREE_HAL_HAVE_LEGACY_LIBRARY_LOADER=1",
@@ -33,11 +33,9 @@
     deps = [
         "//iree/base:api",
         "//iree/base:core_headers",
-        "//iree/base:dynamic_library",
         "//iree/base:flatcc",
         "//iree/base:tracing",
-        "//iree/base/internal:file_io",
-        "//iree/base/internal:file_path",
+        "//iree/base/internal:dynamic_library",
         "//iree/hal:api",
         "//iree/hal/local",
         "//iree/schemas:dylib_executable_def_c_fbs",
diff --git a/iree/hal/local/loaders/CMakeLists.txt b/iree/hal/local/loaders/CMakeLists.txt
index 761b566..d4168d4 100644
--- a/iree/hal/local/loaders/CMakeLists.txt
+++ b/iree/hal/local/loaders/CMakeLists.txt
@@ -16,14 +16,12 @@
   HDRS
     "legacy_library_loader.h"
   SRCS
-    "legacy_library_loader.cc"
+    "legacy_library_loader.c"
   DEPS
     iree::base::api
     iree::base::core_headers
-    iree::base::dynamic_library
     iree::base::flatcc
-    iree::base::internal::file_io
-    iree::base::internal::file_path
+    iree::base::internal::dynamic_library
     iree::base::tracing
     iree::hal::api
     iree::hal::local
diff --git a/iree/hal/local/loaders/legacy_library_loader.cc b/iree/hal/local/loaders/legacy_library_loader.c
similarity index 76%
rename from iree/hal/local/loaders/legacy_library_loader.cc
rename to iree/hal/local/loaders/legacy_library_loader.c
index bebb69b..136877b 100644
--- a/iree/hal/local/loaders/legacy_library_loader.cc
+++ b/iree/hal/local/loaders/legacy_library_loader.c
@@ -14,9 +14,7 @@
 
 #include "iree/hal/local/loaders/legacy_library_loader.h"
 
-#include "iree/base/dynamic_library.h"
-#include "iree/base/internal/file_io.h"
-#include "iree/base/internal/file_path.h"
+#include "iree/base/internal/dynamic_library.h"
 #include "iree/base/target_platform.h"
 #include "iree/base/tracing.h"
 #include "iree/hal/local/local_executable.h"
@@ -74,13 +72,8 @@
   // Flatbuffer definition referencing the executable memory.
   iree_DyLibExecutableDef_table_t def;
 
-  // Temporary files created as part of extraction.
-  // Strings are allocated from the host allocator.
-  iree_host_size_t temp_file_count;
-  iree_string_view_t temp_files[8];
-
   // Loaded platform dynamic library.
-  iree::DynamicLibrary* handle;
+  iree_dynamic_library_t* handle;
 
   // Queried metadata from the library.
   union {
@@ -94,45 +87,14 @@
 
 static iree_status_t iree_hal_legacy_executable_extract_and_load(
     iree_hal_legacy_executable_t* executable, iree_allocator_t host_allocator) {
-  // Write the embedded library out to a temp file, since all of the dynamic
-  // library APIs work with files. We could instead use in-memory files on
-  // platforms where that is convenient.
-  //
-  // TODO(#3845): use dlopen on an fd with either dlopen(/proc/self/fd/NN),
-  // fdlopen, or android_dlopen_ext to avoid needing to write the file to disk.
-  // Can fallback to memfd_create + dlopen where available, and fallback from
-  // that to disk (maybe just windows/mac).
-  std::string library_temp_path;
-  IREE_RETURN_IF_ERROR(
-      iree::file_io::GetTempFile("dylib_executable", &library_temp_path));
-
-// Add platform-specific file extensions so opinionated dynamic library
-// loaders are more likely to find the file:
-#if defined(IREE_PLATFORM_WINDOWS)
-  library_temp_path += ".dll";
-#else
-  library_temp_path += ".so";
-#endif  // IREE_PLATFORM_WINDOWS
-
-  iree_string_view_t library_temp_file = iree_string_view_empty();
-  IREE_RETURN_IF_ERROR(
-      iree_allocator_clone(host_allocator,
-                           iree_make_const_byte_span(library_temp_path.data(),
-                                                     library_temp_path.size()),
-                           (void**)&library_temp_file.data));
-  library_temp_file.size = library_temp_path.size();
-  executable->temp_files[executable->temp_file_count++] = library_temp_file;
-
   flatbuffers_uint8_vec_t embedded_library_vec =
       iree_DyLibExecutableDef_library_embedded_get(executable->def);
-  IREE_RETURN_IF_ERROR(iree::file_io::SetFileContents(
-      library_temp_path,
-      absl::string_view(reinterpret_cast<const char*>(embedded_library_vec),
-                        flatbuffers_uint8_vec_len(embedded_library_vec))));
-
-  std::unique_ptr<iree::DynamicLibrary> handle;
-  IREE_RETURN_IF_ERROR(
-      iree::DynamicLibrary::Load(library_temp_path.c_str(), &handle));
+  IREE_RETURN_IF_ERROR(iree_dynamic_library_load_from_memory(
+      iree_make_cstring_view("aot"),
+      iree_make_const_byte_span(
+          embedded_library_vec,
+          flatbuffers_uint8_vec_len(embedded_library_vec)),
+      IREE_DYNAMIC_LIBRARY_FLAG_NONE, host_allocator, &executable->handle));
 
   flatbuffers_string_t debug_database_filename =
       iree_DyLibExecutableDef_debug_database_filename_get(executable->def);
@@ -140,44 +102,22 @@
       iree_DyLibExecutableDef_debug_database_embedded_get(executable->def);
   if (flatbuffers_string_len(debug_database_filename) &&
       flatbuffers_uint8_vec_len(debug_database_embedded_vec)) {
-    IREE_TRACE_SCOPE0("DyLibExecutable::AttachDebugDatabase");
-    iree_string_view_t library_temp_path_sv = iree_make_string_view(
-        library_temp_path.data(), library_temp_path.size());
-    iree_string_view_t debug_database_filename_sv =
-        iree_make_string_view(debug_database_filename,
-                              flatbuffers_string_len(debug_database_filename));
-    char* debug_database_path = NULL;
-    IREE_RETURN_IF_ERROR(iree_file_path_join(
-        iree_file_path_dirname(library_temp_path_sv),
-        debug_database_filename_sv, host_allocator, &debug_database_path));
-    iree_string_view_t debug_database_path_sv =
-        iree_make_cstring_view(debug_database_path);
-    executable->temp_files[executable->temp_file_count++] =
-        debug_database_path_sv;
-    IREE_IGNORE_ERROR(iree::file_io::SetFileContents(
-        debug_database_path,
-        absl::string_view(
-            reinterpret_cast<const char*>(debug_database_embedded_vec),
+    IREE_RETURN_IF_ERROR(iree_dynamic_library_attach_symbols_from_memory(
+        executable->handle,
+        iree_make_const_byte_span(
+            debug_database_embedded_vec,
             flatbuffers_uint8_vec_len(debug_database_embedded_vec))));
-    handle->AttachDebugDatabase(debug_database_path);
   }
-
-  executable->handle = handle.release();
-
   return iree_ok_status();
 }
 
 static iree_status_t iree_hal_legacy_executable_query_library(
     iree_hal_legacy_executable_t* executable) {
   // Get the exported symbol used to get the library metadata.
-  iree_hal_executable_library_query_fn_t query_fn =
-      (iree_hal_executable_library_query_fn_t)executable->handle->GetSymbol(
-          IREE_HAL_EXECUTABLE_LIBRARY_EXPORT_NAME);
-  if (!query_fn) {
-    return iree_make_status(
-        IREE_STATUS_NOT_FOUND,
-        "executable metadata query function not found in library");
-  }
+  iree_hal_executable_library_query_fn_t query_fn = NULL;
+  IREE_RETURN_IF_ERROR(iree_dynamic_library_lookup_symbol(
+      executable->handle, IREE_HAL_EXECUTABLE_LIBRARY_EXPORT_NAME,
+      (void**)&query_fn));
 
   // Query for a compatible version of the library.
   executable->library.header =
@@ -286,22 +226,7 @@
   iree_allocator_t host_allocator = executable->base.host_allocator;
   IREE_TRACE_ZONE_BEGIN(z0);
 
-#if IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
-  // Leak the library when tracing, since the profiler may still be reading it.
-  // TODO(benvanik): move to an atexit handler instead, verify with ASAN/MSAN
-  // TODO(scotttodd): Make this compatible with testing:
-  //     two test cases, one for each function in the same executable
-  //     first test case passes, second fails to open the file (already open)
-#else
-  delete executable->handle;
-#endif  // IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
-
-  for (iree_host_size_t i = 0; i < executable->temp_file_count; ++i) {
-    iree_string_view_t file_path = executable->temp_files[i];
-    iree::file_io::DeleteFile(std::string(file_path.data, file_path.size))
-        .IgnoreError();
-    iree_allocator_free(host_allocator, (void*)file_path.data);
-  }
+  iree_dynamic_library_release(executable->handle);
 
   iree_hal_local_executable_deinitialize(
       (iree_hal_local_executable_t*)base_executable);
diff --git a/iree/hal/vulkan/BUILD b/iree/hal/vulkan/BUILD
index 4c495c8..d729186 100644
--- a/iree/hal/vulkan/BUILD
+++ b/iree/hal/vulkan/BUILD
@@ -121,19 +121,17 @@
         "vulkan_headers.h",
     ],
     hdrs = [
-        "dynamic_symbol_tables.h",
         "dynamic_symbols.h",
     ],
+    textual_hdrs = [
+        "dynamic_symbol_tables.h",
+    ],
     deps = [
         "//iree/base:core_headers",
-        "//iree/base:dynamic_library",
         "//iree/base:status",
         "//iree/base:tracing",
+        "//iree/base/internal:dynamic_library",
         "//iree/hal/vulkan/util:ref_ptr",
-        "@com_google_absl//absl/base:core_headers",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
         "@iree_vulkan_headers//:vulkan_headers",
     ],
 )
diff --git a/iree/hal/vulkan/CMakeLists.txt b/iree/hal/vulkan/CMakeLists.txt
index 49783de..ae1bd70 100644
--- a/iree/hal/vulkan/CMakeLists.txt
+++ b/iree/hal/vulkan/CMakeLists.txt
@@ -99,19 +99,16 @@
   NAME
     dynamic_symbols
   HDRS
-    "dynamic_symbol_tables.h"
     "dynamic_symbols.h"
+  TEXTUAL_HDRS
+    "dynamic_symbol_tables.h"
   SRCS
     "dynamic_symbols.cc"
     "vulkan_headers.h"
   DEPS
     Vulkan::Headers
-    absl::core_headers
-    absl::memory
-    absl::span
-    absl::strings
     iree::base::core_headers
-    iree::base::dynamic_library
+    iree::base::internal::dynamic_library
     iree::base::status
     iree::base::tracing
     iree::hal::vulkan::util::ref_ptr
diff --git a/iree/hal/vulkan/dynamic_symbols.cc b/iree/hal/vulkan/dynamic_symbols.cc
index 81067c0..91bf60b 100644
--- a/iree/hal/vulkan/dynamic_symbols.cc
+++ b/iree/hal/vulkan/dynamic_symbols.cc
@@ -16,9 +16,6 @@
 
 #include <cstddef>
 
-#include "absl/memory/memory.h"
-#include "absl/strings/str_cat.h"
-#include "absl/types/span.h"
 #include "iree/base/attributes.h"
 #include "iree/base/status.h"
 #include "iree/base/target_platform.h"
@@ -164,16 +161,32 @@
 StatusOr<ref_ptr<DynamicSymbols>> DynamicSymbols::CreateFromSystemLoader() {
   IREE_TRACE_SCOPE0("DynamicSymbols::CreateFromSystemLoader");
 
-  std::unique_ptr<iree::DynamicLibrary> loader_library;
-  IREE_RETURN_IF_ERROR(DynamicLibrary::Load(
-      absl::MakeSpan(kVulkanLoaderSearchNames), &loader_library));
-  auto syms = make_ref<DynamicSymbols>();
-  syms->loader_library_ = std::move(loader_library);
+  iree_dynamic_library_t* loader_library = NULL;
+  iree_status_t status = iree_dynamic_library_load_from_files(
+      IREE_ARRAYSIZE(kVulkanLoaderSearchNames), kVulkanLoaderSearchNames,
+      IREE_DYNAMIC_LIBRARY_FLAG_NONE, iree_allocator_system(), &loader_library);
+  if (iree_status_is_not_found(status)) {
+    iree_status_ignore(status);
+    return iree_make_status(
+        IREE_STATUS_UNAVAILABLE,
+        "Vulkan runtime library not available; ensure installed and on path");
+  } else if (!iree_status_is_ok(status)) {
+    return status;
+  }
 
-  auto* loader_library_ptr = syms->loader_library_.get();
-  IREE_RETURN_IF_ERROR(ResolveFunctions(
-      syms.get(), [loader_library_ptr](const char* function_name) {
-        return loader_library_ptr->GetSymbol<PFN_vkVoidFunction>(function_name);
+  auto syms = make_ref<DynamicSymbols>();
+  syms->loader_library_ = loader_library;
+
+  IREE_RETURN_IF_ERROR(
+      ResolveFunctions(syms.get(), [loader_library](const char* function_name) {
+        PFN_vkVoidFunction fn = NULL;
+        iree_status_t status = iree_dynamic_library_lookup_symbol(
+            loader_library, function_name, (void**)&fn);
+        if (!iree_status_is_ok(status)) {
+          IREE_IGNORE_ERROR(status);
+          return (PFN_vkVoidFunction)NULL;
+        }
+        return fn;
       }));
   syms->FixupExtensionFunctions();
   return syms;
@@ -229,7 +242,11 @@
 
 DynamicSymbols::DynamicSymbols() = default;
 
-DynamicSymbols::~DynamicSymbols() = default;
+DynamicSymbols::~DynamicSymbols() {
+  if (loader_library_) {
+    iree_dynamic_library_release(loader_library_);
+  }
+}
 
 void DynamicSymbols::FixupExtensionFunctions() {
   this->vkGetSemaphoreCounterValue = this->vkGetSemaphoreCounterValue
diff --git a/iree/hal/vulkan/dynamic_symbols.h b/iree/hal/vulkan/dynamic_symbols.h
index 67cff28..d80e339 100644
--- a/iree/hal/vulkan/dynamic_symbols.h
+++ b/iree/hal/vulkan/dynamic_symbols.h
@@ -23,7 +23,7 @@
 #include <functional>
 #include <memory>
 
-#include "iree/base/dynamic_library.h"
+#include "iree/base/internal/dynamic_library.h"
 #include "iree/base/status.h"
 #include "iree/hal/vulkan/dynamic_symbol_tables.h"
 #include "iree/hal/vulkan/util/ref_ptr.h"
@@ -124,7 +124,7 @@
   void FixupExtensionFunctions();
 
   // Optional Vulkan Loader dynamic library.
-  std::unique_ptr<DynamicLibrary> loader_library_;
+  iree_dynamic_library_t* loader_library_ = nullptr;
 };
 
 }  // namespace vulkan
diff --git a/iree/testing/vulkan/iree-run-module-vulkan-gui-main.cc b/iree/testing/vulkan/iree-run-module-vulkan-gui-main.cc
index 0ea4501..29491ba 100644
--- a/iree/testing/vulkan/iree-run-module-vulkan-gui-main.cc
+++ b/iree/testing/vulkan/iree-run-module-vulkan-gui-main.cc
@@ -94,7 +94,8 @@
     *out_contents = std::string{std::istreambuf_iterator<char>(std::cin),
                                 std::istreambuf_iterator<char>()};
   } else {
-    IREE_RETURN_IF_ERROR(file_io::GetFileContents(module_file, out_contents));
+    IREE_RETURN_IF_ERROR(
+        file_io::GetFileContents(module_file.c_str(), out_contents));
   }
   return OkStatus();
 }
diff --git a/iree/tools/BUILD b/iree/tools/BUILD
index 3931f8a..ca16070 100644
--- a/iree/tools/BUILD
+++ b/iree/tools/BUILD
@@ -73,6 +73,7 @@
     name = "iree-dump-module",
     srcs = ["iree-dump-module-main.cc"],
     deps = [
+        "//iree/base:status",
         "//iree/base/internal:file_io",
         "//iree/schemas:bytecode_module_def_c_fbs",
     ],
diff --git a/iree/tools/CMakeLists.txt b/iree/tools/CMakeLists.txt
index 550059f..2c9c330 100644
--- a/iree/tools/CMakeLists.txt
+++ b/iree/tools/CMakeLists.txt
@@ -115,6 +115,7 @@
   DEPS
     flatcc::runtime
     iree::base::internal::file_io
+    iree::base::status
     iree::schemas::bytecode_module_def_c_fbs
 )
 
diff --git a/iree/tools/iree-benchmark-module-main.cc b/iree/tools/iree-benchmark-module-main.cc
index 058dc2b..a225339 100644
--- a/iree/tools/iree-benchmark-module-main.cc
+++ b/iree/tools/iree-benchmark-module-main.cc
@@ -121,7 +121,8 @@
     *out_contents = std::string{std::istreambuf_iterator<char>(std::cin),
                                 std::istreambuf_iterator<char>()};
   } else {
-    IREE_RETURN_IF_ERROR(file_io::GetFileContents(module_file, out_contents));
+    IREE_RETURN_IF_ERROR(
+        file_io::GetFileContents(module_file.c_str(), out_contents));
   }
   return OkStatus();
 }
diff --git a/iree/tools/iree-check-module-main.cc b/iree/tools/iree-check-module-main.cc
index 928b066..1a310b6 100644
--- a/iree/tools/iree-check-module-main.cc
+++ b/iree/tools/iree-check-module-main.cc
@@ -97,7 +97,7 @@
                               std::istreambuf_iterator<char>()};
   } else {
     IREE_RETURN_IF_ERROR(
-        file_io::GetFileContents(module_file_path, &module_data));
+        file_io::GetFileContents(module_file_path.c_str(), &module_data));
   }
 
   iree_vm_module_t* input_module = nullptr;
diff --git a/iree/tools/iree-dump-module-main.cc b/iree/tools/iree-dump-module-main.cc
index d83e429..273246a 100644
--- a/iree/tools/iree-dump-module-main.cc
+++ b/iree/tools/iree-dump-module-main.cc
@@ -17,6 +17,7 @@
 #include <utility>
 
 #include "iree/base/internal/file_io.h"
+#include "iree/base/status.h"
 #include "iree/schemas/bytecode_module_def_json_printer.h"
 
 // Today we just print to JSON. We could do something more useful (size
@@ -31,11 +32,7 @@
     return 1;
   }
   std::string module_contents;
-  auto status = iree::file_io::GetFileContents(argv[1], &module_contents);
-  if (!status.ok()) {
-    std::cerr << status;
-    return 1;
-  }
+  IREE_CHECK_OK(iree::file_io::GetFileContents(argv[1], &module_contents));
 
   // Print direct to stdout.
   flatcc_json_printer_t printer;
diff --git a/iree/tools/iree-run-module-main.cc b/iree/tools/iree-run-module-main.cc
index f770b22..d4015a3 100644
--- a/iree/tools/iree-run-module-main.cc
+++ b/iree/tools/iree-run-module-main.cc
@@ -62,7 +62,8 @@
     *out_contents = std::string{std::istreambuf_iterator<char>(std::cin),
                                 std::istreambuf_iterator<char>()};
   } else {
-    IREE_RETURN_IF_ERROR(file_io::GetFileContents(module_file, out_contents));
+    IREE_RETURN_IF_ERROR(
+        file_io::GetFileContents(module_file.c_str(), out_contents));
   }
   return OkStatus();
 }
diff --git a/iree/tools/utils/vm_util.cc b/iree/tools/utils/vm_util.cc
index fa502ad..296d7fc 100644
--- a/iree/tools/utils/vm_util.cc
+++ b/iree/tools/utils/vm_util.cc
@@ -182,7 +182,7 @@
     iree_hal_allocator_t* allocator, const std::string& filename,
     iree_vm_list_t** out_list) {
   std::string contents;
-  IREE_RETURN_IF_ERROR(file_io::GetFileContents(filename, &contents));
+  IREE_RETURN_IF_ERROR(file_io::GetFileContents(filename.c_str(), &contents));
   absl::InlinedVector<absl::string_view, 4> input_views(
       absl::StrSplit(contents, '\n', absl::SkipEmpty()));
   return ParseToVariantList(descs, allocator, input_views, out_list);