Skip to content

[clang-repl] Implement LoadDynamicLibrary for clang-repl wasm use cases #133037

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 1, 2025

Conversation

anutosh491
Copy link
Contributor

@anutosh491 anutosh491 commented Mar 26, 2025

Currently we don't make use of the JIT for the wasm use cases so the approach using the execution engine won't work in these cases.

Rather if we use dlopen. We should be able to do the following (demonstrating through a toy project)

  1. Make use of LoadDynamicLibrary through the given implementation
extern "C" EMSCRIPTEN_KEEPALIVE int load_library(const char *name) {
  auto Err = Interp->LoadDynamicLibrary(name);
  if (Err) {
    llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "load_library error: ");
    return -1;
  }
  return 0;
}
  1. Add a button to call load_library once the library has been added in our MEMFS (currently we have symengine built as a SIDE MODULE and we are loading it)
Screen.Recording.2025-03-26.at.11.00.30.AM.mov

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Mar 26, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 26, 2025

@llvm/pr-subscribers-clang

Author: Anutosh Bhat (anutosh491)

Changes

Full diff: https://github.com/llvm/llvm-project/pull/133037.diff

1 Files Affected:

  • (modified) clang/lib/Interpreter/Interpreter.cpp (+10)
diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp
index fa4c1439c9261..b4c68f340abc0 100644
--- a/clang/lib/Interpreter/Interpreter.cpp
+++ b/clang/lib/Interpreter/Interpreter.cpp
@@ -18,6 +18,7 @@
 #include "llvm/Support/VirtualFileSystem.h"
 #ifdef __EMSCRIPTEN__
 #include "Wasm.h"
+#include <dlfcn.h>
 #endif // __EMSCRIPTEN__
 
 #include "clang/AST/ASTConsumer.h"
@@ -711,6 +712,14 @@ llvm::Error Interpreter::Undo(unsigned N) {
 }
 
 llvm::Error Interpreter::LoadDynamicLibrary(const char *name) {
+#ifdef __EMSCRIPTEN__
+  void* handle = dlopen(name, RTLD_NOW | RTLD_GLOBAL);
+  if (!handle) {
+    llvm::errs() << dlerror() << '\n';
+    return llvm::make_error<llvm::StringError>(
+        "Failed to load dynamic library", llvm::inconvertibleErrorCode());
+  }
+#else
   auto EE = getExecutionEngine();
   if (!EE)
     return EE.takeError();
@@ -722,6 +731,7 @@ llvm::Error Interpreter::LoadDynamicLibrary(const char *name) {
     EE->getMainJITDylib().addGenerator(std::move(*DLSG));
   else
     return DLSG.takeError();
+#endif
 
   return llvm::Error::success();
 }

Copy link

github-actions bot commented Mar 26, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@anutosh491
Copy link
Contributor Author

For anyone concerned,

Such a feature would be tested out in downstream projects like cppinterop and xeus-cpp as currently llvm doesn't have a concrete structure to test clang-repl in the browser.

return llvm::make_error<llvm::StringError>("Failed to load dynamic library",
llvm::inconvertibleErrorCode());
}
#else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we can’t improve llvm::orc::DynamicLibrarySearchGenerator::Load?

cc: @lhames

Copy link
Contributor Author

@anutosh491 anutosh491 Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks for the suggestion.

I did some surface level introspection on DynamicLibrarySearchGenerator::Load and I see it calls dlopen internally but I think the handle is wrapped in a DynamicLibrarySearchGenerator, which lets the ORC JIT symbol resolution system look inside the .so for symbols during later getSymbolAddress(...) calls.

So even if DynamicLibrarySearchGenerator::Load(...) works under WASM (which it might), it:
i) Creates a JITDylib search generator
ii) Which requires an ORC execution engine
iii) Which we don't make use of correct ? (we're using WasmIncrementalExecutor instead)

So as per what I see DynamicLibrarySearchGenerator infrastructure creates symbol generators for ORC JIT Dylibs, which isn’t used in the wasm execution model. So while the underlying dlopen(...) mechanism is similar, DynamicLibrarySearchGenerator wouldn’t integrate meaningfully unless the wasm executor also moved to using ORC JIT infrastructure, which it currently doesn’t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, path resolution isn't a concern in the wasm case for a few reasons (which is why a simple combination of dlopen + dlsym should do the job isn't it ?)

  1. All dynamic libraries are preloaded into MEMFS at known paths during compilation or initialization (e.g., via Emscripten’s --preload-file flag). This means the library’s location is fully deterministic at runtime.

  2. No concept of system-level search paths like LD_LIBRARY_PATH in the browser — we can’t rely on an OS-level dynamic loader. Instead, we always load by the full path (e.g., dlopen("/libsymengine.so")), which resolves directly in the virtual MEMFS.

  3. Because users control the preload path, and because dlopen in Emscripten expects an exact match in the in-memory filesystem, we don’t need infrastructure for path lookup, search path prioritization, or canonicalization. A simple, direct path-based dlopen is all that’s required.

Copy link
Contributor Author

@anutosh491 anutosh491 Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check how we can use the changes here in cppinterop to get dynamic library loaded and symbol addresses fetched at runtime (just needs some changes to LoadLibrary and no change to GetFunctionAddress)

compiler-research/CppInterOp@main...anutosh491:CppInterOp:shared_libs

Everything boils down to preload as we know where in the MEMFS our shared lib exists !

The tests would pass as such

test 1
    Start 1: cppinterop-DynamicLibraryManagerTests

........ logs .....
.......... logs .....
1: End of search list.
1: [       OK ] DynamicLibraryManagerTest.BasicSymbolLookup (577 ms)
1: [----------] 2 tests from DynamicLibraryManagerTest (580 ms total)
1: 
1: [----------] Global test environment tear-down
1: [==========] 2 tests from 1 test suite ran. (591 ms total)
1: [  PASSED  ] 1 test.
1: [  SKIPPED ] 1 test, listed below:
1: [  SKIPPED ] DynamicLibraryManagerTest.Sanity

@vgvassilev
Copy link
Contributor

We really need to figure out how to add in-tree tests.

@anutosh491
Copy link
Contributor Author

We really need to figure out how to add in-tree tests.

Yupp sadly I was only able to test this through cppinterop and xeus-cpp as I've commented above (#133037 (comment)) where it works as expected :\

I shall take out some time in the near future to ideate on this.

@vgvassilev
Copy link
Contributor

We really need to figure out how to add in-tree tests.

Yupp sadly I was only able to test this through cppinterop and xeus-cpp as I've commented above (#133037 (comment)) where it works as expected :\

I shall take out some time in the near future to ideate on this.

I am wondering if we can add such CI for llvm, too... maybe you can discuss that at the llvm discord's infrastructure channel...

@anutosh491
Copy link
Contributor Author

I am wondering if we can add such CI for llvm, too... maybe you can discuss that at the llvm discord's infrastructure channel...

Absolutely, I shall do that and get back to you on this (I think there are people at llvm looking into llvm+emscripten use cases and should support something like this)

Shall keep you updated.

In the meantime would be great to have your views on the above ... and possibly if you could give me the green flag here :)
I think the reasoning sits well in a wasm context and would expand the interpreter's functionality to put any 3rd party/custom shared lib to use (issue description shows the interpreter loading symengine at runtime) . We also have a decent framework now through downstream projects to test something like the above.

Copy link
Contributor

@vgvassilev vgvassilev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm! I still feel uncomfortable landing changes without in-tree tests. We need to invest into developing in-tree testing infrastructure for emscripten.

@vgvassilev vgvassilev merged commit 8f56394 into llvm:main Apr 1, 2025
11 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Apr 1, 2025

LLVM Buildbot has detected a new failure on builder lldb-aarch64-ubuntu running on linaro-lldb-aarch64-ubuntu while building clang at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/59/builds/15260

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
PASS: lldb-api :: functionalities/inferior-crashing/recursive-inferior/TestRecursiveInferiorStep.py (502 of 2111)
PASS: lldb-api :: functionalities/inferior-assert/TestInferiorAssert.py (503 of 2111)
PASS: lldb-api :: functionalities/inline-sourcefile/TestInlineSourceFiles.py (504 of 2111)
PASS: lldb-api :: functionalities/inferior-crashing/TestInferiorCrashingStep.py (505 of 2111)
UNSUPPORTED: lldb-api :: functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py (506 of 2111)
PASS: lldb-api :: functionalities/json/object-file/TestObjectFileJSON.py (507 of 2111)
PASS: lldb-api :: functionalities/inferior-crashing/recursive-inferior/TestRecursiveInferior.py (508 of 2111)
PASS: lldb-api :: functionalities/json/symbol-file/TestSymbolFileJSON.py (509 of 2111)
UNSUPPORTED: lldb-api :: functionalities/launch_stop_at_entry/TestStopAtEntry.py (510 of 2111)
PASS: lldb-api :: functionalities/jitloader_gdb/TestJITLoaderGDB.py (511 of 2111)
FAIL: lldb-api :: functionalities/lazy-loading/TestLazyLoading.py (512 of 2111)
******************** TEST 'lldb-api :: functionalities/lazy-loading/TestLazyLoading.py' FAILED ********************
Script:
--
/usr/bin/python3.10 /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./lib --env LLVM_INCLUDE_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/include --env LLVM_TOOLS_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin --arch aarch64 --build-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex --lldb-module-cache-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/lldb --compiler /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/clang --dsymutil /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin --lldb-obj-root /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/tools/lldb --lldb-libs-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./lib /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/lazy-loading -p TestLazyLoading.py
--
Exit Code: -11

Command Output (stdout):
--
lldb version 21.0.0git (https://github.com/llvm/llvm-project.git revision 8f56394487a4d454be0637667267ad37bd636d0f)
  clang revision 8f56394487a4d454be0637667267ad37bd636d0f
  llvm revision 8f56394487a4d454be0637667267ad37bd636d0f
Skipping the following test categories: ['libc++', 'dsym', 'gmodules', 'debugserver', 'objc']

--
Command Output (stderr):
--
PASS: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_addr_of_struct (TestLazyLoading.TestCase)
PASS: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_arithmetic_expression_in_main (TestLazyLoading.TestCase)
PASS: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_class_function_access_member (TestLazyLoading.TestCase)
PASS: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_printing_local_variable_in_other_struct_func (TestLazyLoading.TestCase)
PASS: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_printing_struct_with_multiple_locals (TestLazyLoading.TestCase)
----------------------------------------------------------------------
Ran 5 tests in 1.274s

OK

--

********************
PASS: lldb-api :: functionalities/load_lazy/TestLoadUsingLazyBind.py (513 of 2111)
PASS: lldb-api :: functionalities/gdb_remote_client/TestXMLRegisterFlags.py (514 of 2111)
PASS: lldb-api :: functionalities/load_using_paths/TestLoadUsingPaths.py (515 of 2111)
PASS: lldb-api :: functionalities/location-list-lookup/TestLocationListLookup.py (516 of 2111)
PASS: lldb-api :: functionalities/load_after_attach/TestLoadAfterAttach.py (517 of 2111)
PASS: lldb-api :: functionalities/limit-debug-info/TestLimitDebugInfo.py (518 of 2111)
PASS: lldb-api :: functionalities/inline-stepping/TestInlineStepping.py (519 of 2111)
PASS: lldb-api :: functionalities/memory/big-read/TestMemoryReadMaximumSize.py (520 of 2111)

@anutosh491 anutosh491 deleted the dynamic_libs branch April 1, 2025 14:15
@anutosh491
Copy link
Contributor Author

The above failure reported doesn't look related. Cherry picking commit for 20.1.3

/cherry-pick 8f56394

@llvmbot
Copy link
Member

llvmbot commented Apr 2, 2025

The above failure reported doesn't look related. Cherry picking commit for 20.1.3

/cherry-pick 8f56394

Error: Command failed due to missing milestone.

Ankur-0429 pushed a commit to Ankur-0429/llvm-project that referenced this pull request Apr 2, 2025
…es (llvm#133037)

**Currently we don't make use of the JIT for the wasm use cases so the
approach using the execution engine won't work in these cases.**

Rather if we use dlopen. We should be able to do the following
(demonstrating through a toy project)

1) Make use of LoadDynamicLibrary through the given implementation

```
extern "C" EMSCRIPTEN_KEEPALIVE int load_library(const char *name) {
  auto Err = Interp->LoadDynamicLibrary(name);
  if (Err) {
    llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "load_library error: ");
    return -1;
  }
  return 0;
}
```
2) Add a button to call load_library once the library has been added in
our MEMFS (currently we have symengine built as a SIDE MODULE and we are
loading it)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants