[llvm] [ORC] Add lookupAndApply, replacing buildProxies (PR #216658)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 17 02:40:44 PDT 2026


https://github.com/lhames updated https://github.com/llvm/llvm-project/pull/216658

>From 288d819a35c3ce9262ed84e0f4757eb3dac7af7a Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Mon, 17 Aug 2026 13:57:12 +1000
Subject: [PATCH] [ORC] Add lookupAndApply, replacing buildProxies

lookupAndApply composes a single atomic lookup from independent
operations, each contributing symbols and returning a handler to run on
the result. Two gains over buildProxies:

- One lookup binds everything, so every handler sees a consistent view
  of the JITDylib. (buildProxies did a separate lookup per proxy)

- Each name sits next to the variable it binds, rather than going into a
  lookup set in one place and coming back out of a SymbolMap elsewhere.

recordAddr and recordProxy cover the cases we have; recordProxy is in
its own header so holding a Proxy doesn't pull in the machinery to build
one. Prepare functions are independent, so two may request the same
symbol: the composed set goes through SymbolLookupSet::mergeEntries.

Proxy::Create, ProxyInit and proxyInit are removed. ProxySpecs stay
public, so operations can still be resolved under non-default names. The
asynchronous form has no in-tree callers yet.
---
 .../Orc/EPCGenericDylibManagerSPS.h           |   2 +-
 .../Orc/EPCGenericJITLinkMemoryManagerSPS.h   |   2 +-
 .../Orc/EPCGenericMemoryAccessSPS.h           |   2 +-
 .../llvm/ExecutionEngine/Orc/LookupAndApply.h | 111 ++++++++++++++
 llvm/include/llvm/ExecutionEngine/Orc/Proxy.h |  67 --------
 .../llvm/ExecutionEngine/Orc/RecordProxy.h    |  65 ++++++++
 llvm/lib/ExecutionEngine/Orc/CMakeLists.txt   |   1 +
 llvm/lib/ExecutionEngine/Orc/COFFPlatform.cpp |  10 +-
 .../Orc/COFFVCRuntimeSupport.cpp              |   9 +-
 .../Orc/EPCGenericDylibManagerSPS.cpp         |  19 ++-
 .../Orc/EPCGenericJITLinkMemoryManagerSPS.cpp |  24 ++-
 .../Orc/EPCGenericMemoryAccessSPS.cpp         |  30 ++--
 .../ExecutionEngine/Orc/LookupAndApply.cpp    |  70 +++++++++
 .../ExecutionEngine/Orc/CMakeLists.txt        |   1 +
 .../Orc/LookupAndApplyTest.cpp                | 145 ++++++++++++++++++
 .../ExecutionEngine/Orc/ProxyTest.cpp         | 116 +++++---------
 .../llvm/lib/ExecutionEngine/Orc/BUILD.gn     |   1 +
 .../unittests/ExecutionEngine/Orc/BUILD.gn    |   1 +
 18 files changed, 489 insertions(+), 187 deletions(-)
 create mode 100644 llvm/include/llvm/ExecutionEngine/Orc/LookupAndApply.h
 create mode 100644 llvm/include/llvm/ExecutionEngine/Orc/RecordProxy.h
 create mode 100644 llvm/lib/ExecutionEngine/Orc/LookupAndApply.cpp
 create mode 100644 llvm/unittests/ExecutionEngine/Orc/LookupAndApplyTest.cpp

diff --git a/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.h b/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.h
index 46c180eda17e5..3b01da8b93300 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.h
@@ -14,7 +14,7 @@
 // controller-interface descriptor in Shared/SPSCI/NativeDylibManagerSPSCI.h,
 // which supplies the wrapper name and wire signature. The specs are public so
 // that clients can resolve the operations under non-default names, using
-// proxyInit<Spec>(&P, Name) with buildProxies.
+// recordProxy<Spec>(&P, Name) with lookupAndApply.
 //
 //===----------------------------------------------------------------------===//
 
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.h b/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.h
index c6978f5765616..1da5bc0e5c186 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.h
@@ -14,7 +14,7 @@
 // controller-interface descriptor in Shared/SPSCI/SimpleNativeMemoryMapSPSCI.h,
 // which supplies the wrapper name and wire signature. The specs are public so
 // that clients can resolve the operations under non-default names, using
-// proxyInit<Spec>(&P, Name) with buildProxies.
+// recordProxy<Spec>(&P, Name) with lookupAndApply.
 //
 //===----------------------------------------------------------------------===//
 
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.h b/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.h
index 21d0efa539360..f55796aaa9256 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.h
@@ -14,7 +14,7 @@
 // controller-interface descriptor in Shared/SPSCI/MemoryAccessSPSCI.h, which
 // supplies the wrapper name and wire signature. The specs are public so that
 // clients can resolve the operations under non-default names, using
-// proxyInit<Spec>(&P, Name) with buildProxies.
+// recordProxy<Spec>(&P, Name) with lookupAndApply.
 //
 //===----------------------------------------------------------------------===//
 
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/LookupAndApply.h b/llvm/include/llvm/ExecutionEngine/Orc/LookupAndApply.h
new file mode 100644
index 0000000000000..8ab5b3273aadf
--- /dev/null
+++ b/llvm/include/llvm/ExecutionEngine/Orc/LookupAndApply.h
@@ -0,0 +1,111 @@
+//===- LookupAndApply.h - Compose a lookup from handlers --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Compose an ExecutionSession lookup out of independent operations, each of
+// which contributes symbols to the lookup and then acts on the addresses those
+// symbols resolve to.
+//
+// The motivating use is binding a small, fixed set of controller-side variables
+// -- e.g. the proxies and instance address that make up an executor-side
+// service's handle -- with a single atomic lookup. Handlers are not limited to
+// that: they receive the whole result map and may do as they please with it.
+// Either way the operations do all the work, and the return path only signals
+// success or failure.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_ORC_LOOKUPANDAPPLY_H
+#define LLVM_EXECUTIONENGINE_ORC_LOOKUPANDAPPLY_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/FunctionExtras.h"
+#include "llvm/ExecutionEngine/Orc/Core.h"
+#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Error.h"
+
+namespace llvm::orc {
+
+/// Acts on the result of a completed lookup.
+///
+/// Produced by a LookupPrepareFn once it has added its symbols, so that it can
+/// capture their interned names rather than re-interning them here.
+using LookupApplyFn = unique_function<void(const SymbolMap &M)>;
+
+/// Contributes symbols to a lookup, and returns the function that will act on
+/// the result.
+///
+/// A prepare function may contribute any number of symbols, so one of them can
+/// stand for a whole service's worth of bindings. The applicator it returns
+/// runs only if the lookup succeeds.
+///
+/// The call operator is const so that these can be passed as a braced list (see
+/// lookupAndApply): they hold no mutable state.
+using LookupPrepareFn = unique_function<LookupApplyFn(
+    SymbolLookupSet &LS, ExecutionSession &ES) const>;
+
+/// Resolve the symbols contributed by every prepare function with a single
+/// lookup, then let each of their applicators act on the result.
+///
+/// Because one lookup covers them all, every applicator observes a single
+/// consistent view of the search order.
+///
+/// Prepare functions need not coordinate: if two of them ask for the same
+/// symbol the contributed entries are merged (see
+/// SymbolLookupSet::mergeEntries), and each applicator still reads its own
+/// value out of the result.
+///
+/// Asynchronous version: OnApplied is called once every applicator has run, or
+/// with an error if the lookup failed (in which case none of them run).
+///
+/// The prepare functions are only used during this call -- they are asked for
+/// their symbols up front, and only their applicators are retained -- so a
+/// braced list or other temporary is safe here.
+LLVM_ABI void lookupAndApply(unique_function<void(Error)> OnApplied,
+                             ExecutionSession &ES, LookupKind K,
+                             const JITDylibSearchOrder &SearchOrder,
+                             ArrayRef<LookupPrepareFn> PrepareFns);
+
+/// Blocking version of lookupAndApply above.
+LLVM_ABI Error lookupAndApply(ExecutionSession &ES, LookupKind K,
+                              const JITDylibSearchOrder &SearchOrder,
+                              ArrayRef<LookupPrepareFn> PrepareFns);
+
+/// lookupAndApply with a static lookup in the given JITDylib.
+LLVM_ABI void lookupAndApply(unique_function<void(Error)> OnApplied,
+                             JITDylib &JD,
+                             ArrayRef<LookupPrepareFn> PrepareFns);
+
+/// lookupAndApply with a static lookup in the given JITDylib. Blocking
+/// version.
+LLVM_ABI Error lookupAndApply(JITDylib &JD,
+                              ArrayRef<LookupPrepareFn> PrepareFns);
+
+/// Records the address of the symbol with the given name.
+///
+/// If the symbol is weakly referenced and not found then *A is set to null.
+///
+/// Name must remain valid until the lookupAndApply call it is passed to has
+/// collected its symbols: it is interned up front, and only the interned name
+/// is retained.
+inline LookupPrepareFn
+recordAddr(StringRef Name, ExecutorAddr *A,
+           SymbolLookupFlags LF = SymbolLookupFlags::RequiredSymbol) {
+  return [Name, A, LF](SymbolLookupSet &LS,
+                       ExecutionSession &ES) -> LookupApplyFn {
+    auto N = ES.intern(Name);
+    LS.add(N, LF);
+    return [A, N = std::move(N)](const SymbolMap &M) {
+      *A = M.lookup(N).getAddress();
+    };
+  };
+}
+
+} // namespace llvm::orc
+
+#endif // LLVM_EXECUTIONENGINE_ORC_LOOKUPANDAPPLY_H
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/Proxy.h b/llvm/include/llvm/ExecutionEngine/Orc/Proxy.h
index 2504f6217620c..0d994ad0562d6 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/Proxy.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/Proxy.h
@@ -115,24 +115,6 @@ class Proxy<RetT(ArgTs...)> : public ProxyBase {
   Proxy(DispatchFn Dispatch, ExecutorAddr CalleeAddr)
       : ProxyBase(CalleeAddr), Dispatch(Dispatch) {}
 
-  static Expected<Proxy> Create(DispatchFn Dispatch, JITDylib &JD,
-                                StringRef Name, SymbolLookupFlags LF) {
-    auto &ES = JD.getExecutionSession();
-    if (auto CalleeSyms = ES.lookup(makeJITDylibSearchOrder(&JD),
-                                    SymbolLookupSet{ES.intern(Name), LF})) {
-      if (!CalleeSyms->empty())
-        return Proxy(Dispatch, CalleeSyms->begin()->second.getAddress());
-      assert(LF == SymbolLookupFlags::WeaklyReferencedSymbol);
-      return Proxy();
-    } else
-      return CalleeSyms.takeError();
-  }
-
-  static Expected<Proxy> Create(DispatchFn Dispatch, ExecutionSession &ES,
-                                StringRef Name, SymbolLookupFlags LF) {
-    return Create(Dispatch, ES.getBootstrapJITDylib(), Name, LF);
-  }
-
   /// Asynchronously invoke the operation with the given Args, delivering its
   /// result (or an error) to OnComplete.
   void operator()(unique_function<void(ErrorRetT)> OnComplete,
@@ -156,55 +138,6 @@ class Proxy<RetT(ArgTs...)> : public ProxyBase {
   DispatchFn Dispatch = nullptr;
 };
 
-template <typename FnT> struct ProxyInit {
-  Proxy<FnT> *P = nullptr;
-  typename Proxy<FnT>::DispatchFn Dispatch = nullptr;
-  StringRef Name;
-  SymbolLookupFlags LookupFlags = SymbolLookupFlags::RequiredSymbol;
-};
-
-template <typename FnT>
-ProxyInit<FnT>
-proxyInit(Proxy<FnT> *P, typename Proxy<FnT>::DispatchFn Dispatch,
-          StringRef Name,
-          SymbolLookupFlags LookupFlags = SymbolLookupFlags::RequiredSymbol) {
-  return {P, Dispatch, Name, LookupFlags};
-}
-
-template <typename ProxySpecT, typename FnT>
-ProxyInit<FnT>
-proxyInit(Proxy<FnT> *P,
-          SymbolLookupFlags LookupFlags = SymbolLookupFlags::RequiredSymbol) {
-  return {P, ProxySpecT::dispatch, ProxySpecT::Name, LookupFlags};
-}
-
-template <typename ProxySpecT, typename FnT>
-ProxyInit<FnT>
-proxyInit(Proxy<FnT> *P, StringRef Name,
-          SymbolLookupFlags LookupFlags = SymbolLookupFlags::RequiredSymbol) {
-  return {P, ProxySpecT::dispatch, Name, LookupFlags};
-}
-
-/// buildProxies base case.
-inline Error buildProxies(JITDylib &JD) { return Error::success(); }
-
-/// buildProxies: Given an ExecutionSession, use BootstrapJITDylib.
-template <typename... FnTs>
-Error buildProxies(ExecutionSession &ES, ProxyInit<FnTs>... PIs) {
-  return buildProxies(ES.getBootstrapJITDylib(), PIs...);
-}
-
-/// Build a sequence of proxies from their respective specs.
-template <typename FnT, typename... FnTs>
-Error buildProxies(JITDylib &JD, ProxyInit<FnT> PI, ProxyInit<FnTs>... PIs) {
-  if (auto POrErr =
-          Proxy<FnT>::Create(PI.Dispatch, JD, PI.Name, PI.LookupFlags))
-    *PI.P = std::move(*POrErr);
-  else
-    return POrErr.takeError();
-  return buildProxies(JD, PIs...);
-}
-
 } // namespace llvm::orc
 
 #endif // LLVM_EXECUTIONENGINE_ORC_PROXY_H
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/RecordProxy.h b/llvm/include/llvm/ExecutionEngine/Orc/RecordProxy.h
new file mode 100644
index 0000000000000..6f4c8cdb2a672
--- /dev/null
+++ b/llvm/include/llvm/ExecutionEngine/Orc/RecordProxy.h
@@ -0,0 +1,65 @@
+//===- RecordProxy.h - Build a Proxy from a lookup --------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// lookupAndApply operations that build Proxy objects over the symbols they
+// resolve.
+//
+// This is kept out of Proxy.h so that clients holding or calling a Proxy do not
+// have to see the lookup machinery used to build one.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_ORC_RECORDPROXY_H
+#define LLVM_EXECUTIONENGINE_ORC_RECORDPROXY_H
+
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
+#include "llvm/ExecutionEngine/Orc/Proxy.h"
+
+namespace llvm::orc {
+
+/// Builds P over the symbol with the given name, dispatching through Dispatch.
+///
+/// If the symbol is weakly referenced and not found then P is left null.
+template <typename FnT>
+LookupPrepareFn
+recordProxy(Proxy<FnT> *P, typename Proxy<FnT>::DispatchFn Dispatch,
+            StringRef Name,
+            SymbolLookupFlags LF = SymbolLookupFlags::RequiredSymbol) {
+  return [P, Dispatch, Name, LF](SymbolLookupSet &LS,
+                                 ExecutionSession &ES) -> LookupApplyFn {
+    auto N = ES.intern(Name);
+    LS.add(N, LF);
+    return [P, Dispatch, N = std::move(N)](const SymbolMap &M) {
+      auto Sym = M.lookup(N);
+      *P = Sym.getAddress() ? Proxy<FnT>(Dispatch, Sym.getAddress())
+                            : Proxy<FnT>();
+    };
+  };
+}
+
+/// Builds P from the given spec, using the spec's default controller-interface
+/// name.
+template <typename ProxySpecT, typename FnT>
+LookupPrepareFn
+recordProxy(Proxy<FnT> *P,
+            SymbolLookupFlags LF = SymbolLookupFlags::RequiredSymbol) {
+  return recordProxy(P, ProxySpecT::dispatch, ProxySpecT::Name, LF);
+}
+
+/// Builds P from the given spec, but resolves it under Name rather than the
+/// spec's default controller-interface name.
+template <typename ProxySpecT, typename FnT>
+LookupPrepareFn
+recordProxy(Proxy<FnT> *P, StringRef Name,
+            SymbolLookupFlags LF = SymbolLookupFlags::RequiredSymbol) {
+  return recordProxy(P, ProxySpecT::dispatch, Name, LF);
+}
+
+} // namespace llvm::orc
+
+#endif // LLVM_EXECUTIONENGINE_ORC_RECORDPROXY_H
diff --git a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
index 26306f36fbdaf..134a4ebea2b1c 100644
--- a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
+++ b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
@@ -43,6 +43,7 @@ add_llvm_component_library(LLVMOrcJIT
   LinkGraphLayer.cpp
   LinkGraphLinkingLayer.cpp
   LoadLinkableFile.cpp
+  LookupAndApply.cpp
   LookupAndRecordAddrs.cpp
   LLJIT.cpp
   MachO.cpp
diff --git a/llvm/lib/ExecutionEngine/Orc/COFFPlatform.cpp b/llvm/lib/ExecutionEngine/Orc/COFFPlatform.cpp
index ca62be4bb44ad..ff342cb03ddf7 100644
--- a/llvm/lib/ExecutionEngine/Orc/COFFPlatform.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/COFFPlatform.cpp
@@ -12,8 +12,10 @@
 #include "llvm/ExecutionEngine/Orc/COFF.h"
 #include "llvm/ExecutionEngine/Orc/CallProxiesSPS.h"
 #include "llvm/ExecutionEngine/Orc/DebugUtils.h"
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
 #include "llvm/ExecutionEngine/Orc/LookupAndRecordAddrs.h"
 #include "llvm/ExecutionEngine/Orc/ObjectFileInterface.h"
+#include "llvm/ExecutionEngine/Orc/RecordProxy.h"
 #include "llvm/ExecutionEngine/Orc/Shared/ObjectFormats.h"
 #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h"
 #include "llvm/Object/COFF.h"
@@ -664,8 +666,9 @@ Error COFFPlatform::runBootstrapSubsectionInitializers(JDBootstrapState &BState,
                                                        StringRef Start,
                                                        StringRef End) {
   CallInt32VoidProxy CallInitializer;
-  if (auto Err = buildProxies(
-          ES, proxyInit<sps::CallInt32VoidProxySpec>(&CallInitializer)))
+  if (auto Err = lookupAndApply(
+          ES.getBootstrapJITDylib(),
+          {recordProxy<sps::CallInt32VoidProxySpec>(&CallInitializer)}))
     return Err;
   for (auto &Initializer : BState.Initializers)
     if (Initializer.first >= Start && Initializer.first <= End &&
@@ -738,7 +741,8 @@ Error COFFPlatform::runSymbolIfExists(JITDylib &PlatformJD,
   if (!AfterCLookupErr) {
     CallInt32VoidProxy CallFn;
     if (auto Err =
-            buildProxies(ES, proxyInit<sps::CallInt32VoidProxySpec>(&CallFn)))
+            lookupAndApply(ES.getBootstrapJITDylib(),
+                           {recordProxy<sps::CallInt32VoidProxySpec>(&CallFn)}))
       return Err;
     auto Res = CallFn(ES, jit_function);
     if (!Res)
diff --git a/llvm/lib/ExecutionEngine/Orc/COFFVCRuntimeSupport.cpp b/llvm/lib/ExecutionEngine/Orc/COFFVCRuntimeSupport.cpp
index a9010a3fc76f7..08159e5647b81 100644
--- a/llvm/lib/ExecutionEngine/Orc/COFFVCRuntimeSupport.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/COFFVCRuntimeSupport.cpp
@@ -11,7 +11,9 @@
 #include "llvm/ExecutionEngine/Orc/COFF.h"
 #include "llvm/ExecutionEngine/Orc/CallProxiesSPS.h"
 #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
 #include "llvm/ExecutionEngine/Orc/LookupAndRecordAddrs.h"
+#include "llvm/ExecutionEngine/Orc/RecordProxy.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/WindowsDriver/MSVCPaths.h"
 
@@ -126,9 +128,10 @@ Error COFFVCRuntimeBootstrapper::initializeStaticVCRuntime(JITDylib &JD) {
 
   CallInt32VoidProxy CallInt32Void;
   CallInt32Int32Proxy CallInt32Int32;
-  if (auto Err = buildProxies(
-          ES, proxyInit<sps::CallInt32VoidProxySpec>(&CallInt32Void),
-          proxyInit<sps::CallInt32Int32ProxySpec>(&CallInt32Int32)))
+  if (auto Err = lookupAndApply(
+          ES.getBootstrapJITDylib(),
+          {recordProxy<sps::CallInt32VoidProxySpec>(&CallInt32Void),
+           recordProxy<sps::CallInt32Int32ProxySpec>(&CallInt32Int32)}))
     return Err;
 
   auto R = CallInt32Int32(ES, jit_scrt_initialize, 0);
diff --git a/llvm/lib/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.cpp b/llvm/lib/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.cpp
index c9c44998bdcb3..eeab5d1da8de2 100644
--- a/llvm/lib/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.cpp
@@ -9,7 +9,8 @@
 #include "llvm/ExecutionEngine/Orc/EPCGenericDylibManagerSPS.h"
 
 #include "llvm/ExecutionEngine/Orc/Core.h"
-#include "llvm/ExecutionEngine/Orc/LookupAndRecordAddrs.h"
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
+#include "llvm/ExecutionEngine/Orc/RecordProxy.h"
 #include "llvm/ExecutionEngine/Orc/Shared/SimpleRemoteEPCUtils.h"
 
 namespace llvm::orc::shared {
@@ -48,15 +49,13 @@ createEPCGenericDylibManager(JITDylib &JD) {
   auto &ES = JD.getExecutionSession();
   EPCGenericDylibManager::Bindings B;
   // Instance is the executor-side manager object -- a data symbol passed as the
-  // first argument to each call, not a wrapper to proxy.
-  if (auto Err = lookupAndRecordAddrs(
-          ES, LookupKind::Static, makeJITDylibSearchOrder({&JD}),
-          {{ES.intern(rt::sps_ci::NativeDylibManagerInstanceName),
-            &B.Instance}}))
-    return std::move(Err);
-  // The proxies resolve to the specs' default (NativeDylibManager) names.
-  if (auto Err = buildProxies(JD, proxyInit<DylibMgrOpenProxySpec>(&B.Open),
-                              proxyInit<DylibMgrResolveProxySpec>(&B.Resolve)))
+  // first argument to each call, not a wrapper to proxy. The proxies resolve to
+  // the specs' default (NativeDylibManager) names.
+  if (auto Err = lookupAndApply(
+          JD,
+          {recordAddr(rt::sps_ci::NativeDylibManagerInstanceName, &B.Instance),
+           recordProxy<DylibMgrOpenProxySpec>(&B.Open),
+           recordProxy<DylibMgrResolveProxySpec>(&B.Resolve)}))
     return std::move(Err);
   return std::make_unique<EPCGenericDylibManager>(ES, std::move(B));
 }
diff --git a/llvm/lib/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.cpp b/llvm/lib/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.cpp
index e57d704ebafd6..a36b756e2403d 100644
--- a/llvm/lib/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.cpp
@@ -9,7 +9,8 @@
 #include "llvm/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.h"
 
 #include "llvm/ExecutionEngine/Orc/Core.h"
-#include "llvm/ExecutionEngine/Orc/LookupAndRecordAddrs.h"
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
+#include "llvm/ExecutionEngine/Orc/RecordProxy.h"
 
 namespace llvm::orc::sps {
 
@@ -18,18 +19,15 @@ createEPCGenericJITLinkMemoryManager(JITDylib &JD) {
   auto &ES = JD.getExecutionSession();
   EPCGenericJITLinkMemoryManager::Bindings B;
   // Instance is the executor-side allocator object -- a data symbol passed as
-  // the first argument to each call, not a wrapper to proxy.
-  if (auto Err = lookupAndRecordAddrs(
-          ES, LookupKind::Static, makeJITDylibSearchOrder({&JD}),
-          {{ES.intern(rt::sps_ci::SimpleNativeMemoryMapInstanceName),
-            &B.Instance}}))
-    return std::move(Err);
-  // The proxies resolve to the specs' default (SimpleNativeMemoryMap) names.
-  if (auto Err =
-          buildProxies(JD, proxyInit<MemMgrReserveProxySpec>(&B.Reserve),
-                       proxyInit<MemMgrInitializeProxySpec>(&B.Initialize),
-                       proxyInit<MemMgrDeinitializeProxySpec>(&B.Deinitialize),
-                       proxyInit<MemMgrReleaseProxySpec>(&B.Release)))
+  // the first argument to each call, not a wrapper to proxy. The proxies
+  // resolve to the specs' default (SimpleNativeMemoryMap) names.
+  if (auto Err = lookupAndApply(
+          JD, {recordAddr(rt::sps_ci::SimpleNativeMemoryMapInstanceName,
+                          &B.Instance),
+               recordProxy<MemMgrReserveProxySpec>(&B.Reserve),
+               recordProxy<MemMgrInitializeProxySpec>(&B.Initialize),
+               recordProxy<MemMgrDeinitializeProxySpec>(&B.Deinitialize),
+               recordProxy<MemMgrReleaseProxySpec>(&B.Release)}))
     return std::move(Err);
   return std::make_unique<EPCGenericJITLinkMemoryManager>(ES, std::move(B));
 }
diff --git a/llvm/lib/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.cpp b/llvm/lib/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.cpp
index 8c5f305c78e94..c061ab841191c 100644
--- a/llvm/lib/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.cpp
@@ -9,6 +9,8 @@
 #include "llvm/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.h"
 
 #include "llvm/ExecutionEngine/Orc/Core.h"
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
+#include "llvm/ExecutionEngine/Orc/RecordProxy.h"
 
 namespace llvm::orc::sps {
 
@@ -17,20 +19,20 @@ createEPCGenericMemoryAccess(JITDylib &JD) {
   auto &ES = JD.getExecutionSession();
   EPCGenericMemoryAccess::Funcs Fns;
   // The proxies resolve to the specs' default controller-interface names.
-  if (auto Err =
-          buildProxies(JD, proxyInit<MemWriteUInt8sProxySpec>(&Fns.WriteUInt8s),
-                       proxyInit<MemWriteUInt16sProxySpec>(&Fns.WriteUInt16s),
-                       proxyInit<MemWriteUInt32sProxySpec>(&Fns.WriteUInt32s),
-                       proxyInit<MemWriteUInt64sProxySpec>(&Fns.WriteUInt64s),
-                       proxyInit<MemWritePointersProxySpec>(&Fns.WritePointers),
-                       proxyInit<MemWriteBuffersProxySpec>(&Fns.WriteBuffers),
-                       proxyInit<MemReadUInt8sProxySpec>(&Fns.ReadUInt8s),
-                       proxyInit<MemReadUInt16sProxySpec>(&Fns.ReadUInt16s),
-                       proxyInit<MemReadUInt32sProxySpec>(&Fns.ReadUInt32s),
-                       proxyInit<MemReadUInt64sProxySpec>(&Fns.ReadUInt64s),
-                       proxyInit<MemReadPointersProxySpec>(&Fns.ReadPointers),
-                       proxyInit<MemReadBuffersProxySpec>(&Fns.ReadBuffers),
-                       proxyInit<MemReadStringsProxySpec>(&Fns.ReadStrings)))
+  if (auto Err = lookupAndApply(
+          JD, {recordProxy<MemWriteUInt8sProxySpec>(&Fns.WriteUInt8s),
+               recordProxy<MemWriteUInt16sProxySpec>(&Fns.WriteUInt16s),
+               recordProxy<MemWriteUInt32sProxySpec>(&Fns.WriteUInt32s),
+               recordProxy<MemWriteUInt64sProxySpec>(&Fns.WriteUInt64s),
+               recordProxy<MemWritePointersProxySpec>(&Fns.WritePointers),
+               recordProxy<MemWriteBuffersProxySpec>(&Fns.WriteBuffers),
+               recordProxy<MemReadUInt8sProxySpec>(&Fns.ReadUInt8s),
+               recordProxy<MemReadUInt16sProxySpec>(&Fns.ReadUInt16s),
+               recordProxy<MemReadUInt32sProxySpec>(&Fns.ReadUInt32s),
+               recordProxy<MemReadUInt64sProxySpec>(&Fns.ReadUInt64s),
+               recordProxy<MemReadPointersProxySpec>(&Fns.ReadPointers),
+               recordProxy<MemReadBuffersProxySpec>(&Fns.ReadBuffers),
+               recordProxy<MemReadStringsProxySpec>(&Fns.ReadStrings)}))
     return std::move(Err);
   return std::make_unique<EPCGenericMemoryAccess>(ES, std::move(Fns));
 }
diff --git a/llvm/lib/ExecutionEngine/Orc/LookupAndApply.cpp b/llvm/lib/ExecutionEngine/Orc/LookupAndApply.cpp
new file mode 100644
index 0000000000000..da0d2c20820db
--- /dev/null
+++ b/llvm/lib/ExecutionEngine/Orc/LookupAndApply.cpp
@@ -0,0 +1,70 @@
+//===- LookupAndApply.cpp - Compose a lookup from handlers ----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
+
+#include "llvm/Support/MSVCErrorWorkarounds.h"
+
+#include <future>
+
+namespace llvm::orc {
+
+void lookupAndApply(unique_function<void(Error)> OnApplied,
+                    ExecutionSession &ES, LookupKind K,
+                    const JITDylibSearchOrder &SearchOrder,
+                    ArrayRef<LookupPrepareFn> PrepareFns) {
+  // Collect the symbols to look up. Each prepare function hands back the
+  // applicator that will act on the result; the prepare functions themselves
+  // are not needed beyond this point.
+  SymbolLookupSet Symbols;
+  std::vector<LookupApplyFn> Applies;
+  Applies.reserve(PrepareFns.size());
+  for (const auto &PF : PrepareFns)
+    Applies.push_back(PF(Symbols, ES));
+
+  // PrepareFns are independent, so two of them may legitimately ask for the
+  // same symbol. ExecutionSession::lookup requires a duplicate-free set, and
+  // the applicators read the result by name, so merging here is invisible to
+  // them.
+  Symbols.mergeEntries();
+
+  ES.lookup(
+      K, SearchOrder, std::move(Symbols), SymbolState::Ready,
+      [Applies = std::move(Applies),
+       OnApplied = std::move(OnApplied)](Expected<SymbolMap> Result) mutable {
+        if (!Result)
+          return OnApplied(Result.takeError());
+        for (auto &Apply : Applies)
+          Apply(*Result);
+        OnApplied(Error::success());
+      },
+      NoDependenciesToRegister);
+}
+
+Error lookupAndApply(ExecutionSession &ES, LookupKind K,
+                     const JITDylibSearchOrder &SearchOrder,
+                     ArrayRef<LookupPrepareFn> PrepareFns) {
+  std::promise<MSVCPError> ResultP;
+  auto ResultF = ResultP.get_future();
+  lookupAndApply([&](Error Err) { ResultP.set_value(std::move(Err)); }, ES, K,
+                 SearchOrder, PrepareFns);
+  return ResultF.get();
+}
+
+void lookupAndApply(unique_function<void(Error)> OnApplied, JITDylib &JD,
+                    ArrayRef<LookupPrepareFn> PrepareFns) {
+  lookupAndApply(std::move(OnApplied), JD.getExecutionSession(),
+                 LookupKind::Static, makeJITDylibSearchOrder(&JD), PrepareFns);
+}
+
+Error lookupAndApply(JITDylib &JD, ArrayRef<LookupPrepareFn> PrepareFns) {
+  return lookupAndApply(JD.getExecutionSession(), LookupKind::Static,
+                        makeJITDylibSearchOrder(&JD), PrepareFns);
+}
+
+} // namespace llvm::orc
diff --git a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
index ac648b3f420bf..798c7426e373b 100644
--- a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
+++ b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
@@ -31,6 +31,7 @@ add_llvm_unittest(OrcJITTests
   LazyCallThroughAndReexportsTest.cpp
   LibraryResolverTest.cpp
   LinkGraphLinkingLayerTest.cpp
+  LookupAndApplyTest.cpp
   LookupAndRecordAddrsTest.cpp
   MachOBuilderTest.cpp
   MangleAndInternerTest.cpp
diff --git a/llvm/unittests/ExecutionEngine/Orc/LookupAndApplyTest.cpp b/llvm/unittests/ExecutionEngine/Orc/LookupAndApplyTest.cpp
new file mode 100644
index 0000000000000..5b29e0f65823b
--- /dev/null
+++ b/llvm/unittests/ExecutionEngine/Orc/LookupAndApplyTest.cpp
@@ -0,0 +1,145 @@
+//===- LookupAndApplyTest.cpp - Test lookupAndApply -----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
+
+#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
+#include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h"
+#include "llvm/Support/MSVCErrorWorkarounds.h"
+#include "llvm/Testing/Support/Error.h"
+
+#include <future>
+
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::orc;
+
+namespace {
+
+// Two arbitrary, distinct addresses to resolve symbols to.
+constexpr uint64_t AddrAValue = 0x1000;
+constexpr uint64_t AddrBValue = 0x2000;
+
+// Define Name -> Addr in JD as an exported absolute symbol.
+static void defineAddr(JITDylib &JD, StringRef Name, ExecutorAddr Addr) {
+  auto &ES = JD.getExecutionSession();
+  cantFail(JD.define(
+      absoluteSymbols({{ES.intern(Name), {Addr, JITSymbolFlags::Exported}}})));
+}
+
+} // namespace
+
+// recordAddr writes the resolved address of a required symbol.
+TEST(LookupAndApplyTest, RecordAddr) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+  auto &JD = ES.getBootstrapJITDylib();
+  defineAddr(JD, "addr_a", ExecutorAddr(AddrAValue));
+
+  ExecutorAddr A;
+  cantFail(lookupAndApply(JD, {recordAddr("addr_a", &A)}));
+  EXPECT_EQ(A, ExecutorAddr(AddrAValue));
+
+  cantFail(ES.endSession());
+}
+
+// A required symbol that is missing fails the lookup.
+TEST(LookupAndApplyTest, RecordAddrRequiredAbsentFails) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  ExecutorAddr A(AddrAValue);
+  EXPECT_THAT_ERROR(
+      lookupAndApply(ES.getBootstrapJITDylib(), {recordAddr("absent", &A)}),
+      Failed());
+
+  cantFail(ES.endSession());
+}
+
+// A weakly-referenced symbol that is missing records a null address, rather
+// than failing the lookup.
+TEST(LookupAndApplyTest, RecordAddrWeaklyReferencedAbsent) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+
+  ExecutorAddr A(AddrAValue);
+  cantFail(lookupAndApply(
+      ES.getBootstrapJITDylib(),
+      {recordAddr("absent", &A, SymbolLookupFlags::WeaklyReferencedSymbol)}));
+  EXPECT_EQ(A, ExecutorAddr());
+
+  cantFail(ES.endSession());
+}
+
+// Several prepare functions in one call are all applied, and a single one may
+// contribute more than one symbol.
+TEST(LookupAndApplyTest, MultiplePrepareFns) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+  auto &JD = ES.getBootstrapJITDylib();
+  defineAddr(JD, "addr_a", ExecutorAddr(AddrAValue));
+  defineAddr(JD, "addr_b", ExecutorAddr(AddrBValue));
+
+  ExecutorAddr A, B, C, D;
+
+  // A composite prepare fn: contributes both names, records both results.
+  auto RecordBoth = [&C, &D](SymbolLookupSet &LS,
+                             ExecutionSession &ES) -> LookupApplyFn {
+    auto NA = ES.intern("addr_a");
+    auto NB = ES.intern("addr_b");
+    LS.add(NA);
+    LS.add(NB);
+    return
+        [&C, &D, NA = std::move(NA), NB = std::move(NB)](const SymbolMap &M) {
+          C = M.lookup(NA).getAddress();
+          D = M.lookup(NB).getAddress();
+        };
+  };
+
+  cantFail(lookupAndApply(
+      JD, {recordAddr("addr_a", &A), recordAddr("addr_b", &B), RecordBoth}));
+
+  EXPECT_EQ(A, ExecutorAddr(AddrAValue));
+  EXPECT_EQ(B, ExecutorAddr(AddrBValue));
+  EXPECT_EQ(C, ExecutorAddr(AddrAValue));
+  EXPECT_EQ(D, ExecutorAddr(AddrBValue));
+
+  cantFail(ES.endSession());
+}
+
+// If the lookup fails then no applicator runs: a failed lookup must not
+// leave some variables written and others not.
+TEST(LookupAndApplyTest, NoApplyOnLookupFailure) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+  auto &JD = ES.getBootstrapJITDylib();
+  defineAddr(JD, "addr_a", ExecutorAddr(AddrAValue));
+
+  ExecutorAddr A, B;
+  // "addr_a" resolves, "absent" does not, so the whole lookup fails.
+  EXPECT_THAT_ERROR(
+      lookupAndApply(JD, {recordAddr("addr_a", &A), recordAddr("absent", &B)}),
+      Failed());
+  EXPECT_EQ(A, ExecutorAddr());
+  EXPECT_EQ(B, ExecutorAddr());
+
+  cantFail(ES.endSession());
+}
+
+// The asynchronous form delivers success once every applicator has run.
+TEST(LookupAndApplyTest, Async) {
+  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
+  auto &JD = ES.getBootstrapJITDylib();
+  defineAddr(JD, "addr_a", ExecutorAddr(AddrAValue));
+
+  ExecutorAddr A;
+  std::promise<MSVCPError> P;
+  auto F = P.get_future();
+  lookupAndApply([&](Error Err) { P.set_value(std::move(Err)); }, JD,
+                 {recordAddr("addr_a", &A)});
+  EXPECT_THAT_ERROR(F.get(), Succeeded());
+  EXPECT_EQ(A, ExecutorAddr(AddrAValue));
+
+  cantFail(ES.endSession());
+}
diff --git a/llvm/unittests/ExecutionEngine/Orc/ProxyTest.cpp b/llvm/unittests/ExecutionEngine/Orc/ProxyTest.cpp
index 21c2e10c12c22..76ca030412048 100644
--- a/llvm/unittests/ExecutionEngine/Orc/ProxyTest.cpp
+++ b/llvm/unittests/ExecutionEngine/Orc/ProxyTest.cpp
@@ -16,6 +16,8 @@
 
 #include "llvm/ExecutionEngine/Orc/Proxy.h"
 #include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
+#include "llvm/ExecutionEngine/Orc/LookupAndApply.h"
+#include "llvm/ExecutionEngine/Orc/RecordProxy.h"
 #include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h"
 #include "llvm/Support/MSVCErrorWorkarounds.h"
 #include "llvm/Testing/Support/Error.h"
@@ -88,7 +90,7 @@ static_assert(
     std::is_same_v<Proxy<Expected<int>(int)>::ErrorRetT, Expected<int>>);
 
 // A minimal ProxySpec-shaped type (static dispatch + Name) for exercising the
-// proxyInit / buildProxies client path without depending on a protocol.
+// recordProxy client path without depending on a protocol.
 struct AddOneSpec {
   static constexpr const char *Name = "add_one";
   static void dispatch(unique_function<void(Expected<int32_t>)> OnComplete,
@@ -138,58 +140,22 @@ TEST(ProxyTest, OperatorBoolAndAccessors) {
   cantFail(ES.endSession());
 }
 
-// Create looks the callee up by name in the bootstrap JITDylib and binds a
-// usable proxy to it (required-symbol, present).
-TEST(ProxyTest, CreateRequiredPresent) {
+// A required (default) recordProxy against a missing symbol fails the whole
+// lookup, rather than yielding a null proxy as the weakly-referenced form does.
+TEST(ProxyTest, RecordProxyRequiredAbsentFails) {
   ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
 
-  auto &JD = ES.getBootstrapJITDylib();
-  cantFail(JD.define(absoluteSymbols(
-      {{ES.intern(AddOneSpec::Name),
-        {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}})));
-
-  Expected<AddOneProxy> Call = AddOneProxy::Create(
-      AddOneDispatch, ES, AddOneSpec::Name, SymbolLookupFlags::RequiredSymbol);
-  ASSERT_THAT_EXPECTED(Call, Succeeded());
-  EXPECT_TRUE(static_cast<bool>(*Call));
-
-  Expected<int32_t> R = (*Call)(ES, 41);
-  ASSERT_THAT_EXPECTED(R, Succeeded());
-  EXPECT_EQ(*R, 42);
-
-  cantFail(ES.endSession());
-}
-
-// A required (default) Create against a missing symbol fails, rather than
-// yielding a null proxy as the weakly-referenced form does.
-TEST(ProxyTest, CreateRequiredAbsentFails) {
-  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
-
-  Expected<AddOneProxy> Call = AddOneProxy::Create(
-      AddOneDispatch, ES, AddOneSpec::Name, SymbolLookupFlags::RequiredSymbol);
-  EXPECT_THAT_EXPECTED(Call, Failed());
-
-  cantFail(ES.endSession());
-}
-
-// A weakly-referenced Create against a missing symbol succeeds, yielding a
-// proxy with a null callee (falsey) rather than an error.
-TEST(ProxyTest, CreateWeaklyReferencedAbsent) {
-  ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
-
-  Expected<AddOneProxy> Call =
-      AddOneProxy::Create(AddOneDispatch, ES, AddOneSpec::Name,
-                          SymbolLookupFlags::WeaklyReferencedSymbol);
-  ASSERT_THAT_EXPECTED(Call, Succeeded());
-  EXPECT_FALSE(static_cast<bool>(*Call));
-  EXPECT_EQ(Call->calleeAddr(), ExecutorAddr());
+  AddOneProxy Call;
+  EXPECT_THAT_ERROR(lookupAndApply(ES.getBootstrapJITDylib(),
+                                   {recordProxy<AddOneSpec>(&Call)}),
+                    Failed());
 
   cantFail(ES.endSession());
 }
 
-// A weakly-referenced Create against a present symbol resolves it, yielding a
-// usable proxy (truthy) bound to the registered address.
-TEST(ProxyTest, CreateWeaklyReferencedPresent) {
+// A weakly-referenced recordProxy against a present symbol resolves it,
+// yielding a usable proxy (truthy) bound to the registered address.
+TEST(ProxyTest, RecordProxyWeaklyReferencedPresent) {
   ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
 
   auto &JD = ES.getBootstrapJITDylib();
@@ -198,19 +164,19 @@ TEST(ProxyTest, CreateWeaklyReferencedPresent) {
       JD.define(absoluteSymbols({{ES.intern(AddOneSpec::Name),
                                   {CalleeAddr, JITSymbolFlags::Exported}}})));
 
-  Expected<AddOneProxy> Call =
-      AddOneProxy::Create(AddOneDispatch, ES, AddOneSpec::Name,
-                          SymbolLookupFlags::WeaklyReferencedSymbol);
-  ASSERT_THAT_EXPECTED(Call, Succeeded());
-  EXPECT_TRUE(static_cast<bool>(*Call));
-  EXPECT_EQ(Call->calleeAddr(), CalleeAddr);
+  AddOneProxy Call;
+  cantFail(lookupAndApply(
+      JD, {recordProxy<AddOneSpec>(
+              &Call, SymbolLookupFlags::WeaklyReferencedSymbol)}));
+  EXPECT_TRUE(static_cast<bool>(Call));
+  EXPECT_EQ(Call.calleeAddr(), CalleeAddr);
 
   cantFail(ES.endSession());
 }
 
-// buildProxies resolves a set of proxies from the bootstrap JITDylib via their
-// specs, exercising the proxyInit / buildProxies client entry point.
-TEST(ProxyTest, BuildProxies) {
+// recordProxy resolves a proxy from the bootstrap JITDylib via its spec,
+// exercising the recordProxy / lookupAndApply client entry point.
+TEST(ProxyTest, RecordProxy) {
   ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
 
   auto &JD = ES.getBootstrapJITDylib();
@@ -219,7 +185,7 @@ TEST(ProxyTest, BuildProxies) {
         {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}})));
 
   AddOneProxy Call;
-  cantFail(buildProxies(ES, proxyInit<AddOneSpec>(&Call)));
+  cantFail(lookupAndApply(JD, {recordProxy<AddOneSpec>(&Call)}));
   ASSERT_TRUE(static_cast<bool>(Call));
 
   Expected<int32_t> R = Call(ES, 41);
@@ -229,9 +195,9 @@ TEST(ProxyTest, BuildProxies) {
   cantFail(ES.endSession());
 }
 
-// buildProxies with an explicitly-supplied dispatch function and name -- the
-// proxyInit overload that takes no spec type.
-TEST(ProxyTest, BuildProxiesExplicitDispatch) {
+// recordProxy with an explicitly-supplied dispatch function and name -- the
+// overload that takes no spec type.
+TEST(ProxyTest, RecordProxyExplicitDispatch) {
   ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
 
   auto &JD = ES.getBootstrapJITDylib();
@@ -240,8 +206,8 @@ TEST(ProxyTest, BuildProxiesExplicitDispatch) {
         {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}})));
 
   AddOneProxy Call;
-  cantFail(
-      buildProxies(ES, proxyInit(&Call, AddOneDispatch, AddOneSpec::Name)));
+  cantFail(lookupAndApply(
+      JD, {recordProxy(&Call, AddOneDispatch, AddOneSpec::Name)}));
   ASSERT_TRUE(static_cast<bool>(Call));
 
   Expected<int32_t> R = Call(ES, 41);
@@ -251,11 +217,11 @@ TEST(ProxyTest, BuildProxiesExplicitDispatch) {
   cantFail(ES.endSession());
 }
 
-// buildProxies with a spec but an overridden lookup name -- the proxyInit
-// overload that takes a spec type plus an explicit name. The symbol is defined
-// only under the override name, so resolving against the spec's default Name
-// would fail; success proves the override is used.
-TEST(ProxyTest, BuildProxiesSpecNameOverride) {
+// recordProxy with a spec but an overridden lookup name -- the overload that
+// takes a spec type plus an explicit name. The symbol is defined only under the
+// override name, so resolving against the spec's default Name would fail;
+// success proves the override is used.
+TEST(ProxyTest, RecordProxySpecNameOverride) {
   ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
 
   auto &JD = ES.getBootstrapJITDylib();
@@ -264,7 +230,8 @@ TEST(ProxyTest, BuildProxiesSpecNameOverride) {
         {ExecutorAddr::fromPtr(addOne), JITSymbolFlags::Exported}}})));
 
   AddOneProxy Call;
-  cantFail(buildProxies(ES, proxyInit<AddOneSpec>(&Call, "add_one_alias")));
+  cantFail(
+      lookupAndApply(JD, {recordProxy<AddOneSpec>(&Call, "add_one_alias")}));
   ASSERT_TRUE(static_cast<bool>(Call));
 
   Expected<int32_t> R = Call(ES, 41);
@@ -274,15 +241,16 @@ TEST(ProxyTest, BuildProxiesSpecNameOverride) {
   cantFail(ES.endSession());
 }
 
-// buildProxies propagates the lookup flags: a weakly-referenced proxyInit for a
-// missing symbol yields a null proxy rather than failing the whole build.
-TEST(ProxyTest, BuildProxiesWeaklyReferencedAbsent) {
+// lookupAndApply propagates the lookup flags: a weakly-referenced recordProxy
+// for a missing symbol yields a null proxy rather than failing the lookup.
+TEST(ProxyTest, RecordProxyWeaklyReferencedAbsent) {
   ExecutionSession ES(cantFail(SelfExecutorProcessControl::Create()));
 
   AddOneProxy Call;
-  cantFail(buildProxies(
-      ES,
-      proxyInit<AddOneSpec>(&Call, SymbolLookupFlags::WeaklyReferencedSymbol)));
+  cantFail(
+      lookupAndApply(ES.getBootstrapJITDylib(),
+                     {recordProxy<AddOneSpec>(
+                         &Call, SymbolLookupFlags::WeaklyReferencedSymbol)}));
   EXPECT_FALSE(static_cast<bool>(Call));
 
   cantFail(ES.endSession());
diff --git a/llvm/utils/gn/secondary/llvm/lib/ExecutionEngine/Orc/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/ExecutionEngine/Orc/BUILD.gn
index 723b65337ba61..c1e51983f8950 100644
--- a/llvm/utils/gn/secondary/llvm/lib/ExecutionEngine/Orc/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/ExecutionEngine/Orc/BUILD.gn
@@ -54,6 +54,7 @@ static_library("Orc") {
     "LinkGraphLayer.cpp",
     "LinkGraphLinkingLayer.cpp",
     "LoadLinkableFile.cpp",
+    "LookupAndApply.cpp",
     "LookupAndRecordAddrs.cpp",
     "MachO.cpp",
     "MachOPlatform.cpp",
diff --git a/llvm/utils/gn/secondary/llvm/unittests/ExecutionEngine/Orc/BUILD.gn b/llvm/utils/gn/secondary/llvm/unittests/ExecutionEngine/Orc/BUILD.gn
index 5659ddfea45e0..285d2dcffb6fa 100644
--- a/llvm/utils/gn/secondary/llvm/unittests/ExecutionEngine/Orc/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/unittests/ExecutionEngine/Orc/BUILD.gn
@@ -29,6 +29,7 @@ unittest("OrcJITTests") {
     "LazyCallThroughAndReexportsTest.cpp",
     "LibraryResolverTest.cpp",
     "LinkGraphLinkingLayerTest.cpp",
+    "LookupAndApplyTest.cpp",
     "LookupAndRecordAddrsTest.cpp",
     "MachOBuilderTest.cpp",
     "MachOPlatformTest.cpp",



More information about the llvm-commits mailing list