From 640a640b681ce3866facdbab99b88b83fe53b655 Mon Sep 17 00:00:00 2001 From: "promptless[bot]" <179508745+promptless[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 08:34:32 +0000 Subject: [PATCH] Documentation updates from Promptless --- pages/runtime.mdx | 1 + pages/runtime/cli.mdx | 12 + pages/runtime/dynamic-linking.mdx | 307 +++++++++++++ pages/runtime/guides/dynamic-linking-c.mdx | 476 +++++++++++++++++++++ 4 files changed, 796 insertions(+) create mode 100644 pages/runtime/dynamic-linking.mdx create mode 100644 pages/runtime/guides/dynamic-linking-c.mdx diff --git a/pages/runtime.mdx b/pages/runtime.mdx index 1e14cc8..3444cd1 100644 --- a/pages/runtime.mdx +++ b/pages/runtime.mdx @@ -21,6 +21,7 @@ To run a package, check out the [Getting Started](/runtime/get-started/) guide. - **Secure** by default. No file, network, or environment access, unless explicitly enabled. - **Fast**. Run WebAssembly at near-native speeds. - **Pluggable**. Embeddable in multiple programming languages +- **Dynamic Linking**. Load and link WebAssembly modules at runtime with threading support. - Compliant with latest WebAssembly Proposals (SIMD, Reference Types, Threads, ...) ## Backends diff --git a/pages/runtime/cli.mdx b/pages/runtime/cli.mdx index 3371292..d352ea4 100644 --- a/pages/runtime/cli.mdx +++ b/pages/runtime/cli.mdx @@ -59,6 +59,18 @@ You can also pass environment variables: wasmer run my_wasi_program.wasm --env MYVAR=MYVALUE -- arg1 arg2 arg3 ``` +### Run Dynamically Linked WebAssembly + +Wasmer supports running WebAssembly modules that use dynamic linking. For modules that load shared libraries at runtime, you may need to mount directories containing the `.so` files: + +```bash copy +wasmer run my_dynamic_program.wasm --mapdir /lib:./libraries +``` + +This makes the `./libraries` directory available to the WebAssembly module as `/lib`, allowing it to find and load shared libraries using `dlopen()`. + +For more information about dynamic linking, see the [Dynamic Linking guide](/runtime/dynamic-linking). + ### Profiling WebAssembly Code You can enable profiling data generation for your WebAssembly code using the `--profiler` option: diff --git a/pages/runtime/dynamic-linking.mdx b/pages/runtime/dynamic-linking.mdx new file mode 100644 index 0000000..b4224c9 --- /dev/null +++ b/pages/runtime/dynamic-linking.mdx @@ -0,0 +1,307 @@ +# Dynamic Linking + +Dynamic linking in Wasmer allows WebAssembly modules to load and link other WebAssembly modules at runtime, enabling more modular and flexible application architectures. This feature supports threading and provides a familiar interface similar to POSIX dynamic linking. + +## Overview + +Dynamic linking enables: +- **Runtime module loading**: Load WebAssembly libraries on-demand using `dlopen` +- **Symbol resolution**: Access functions and global variables from loaded modules using `dlsym` +- **Module unloading**: Clean up loaded modules with `dlclose` +- **Threading support**: Full threading capabilities across dynamically linked modules +- **Dependency management**: Automatic loading of module dependencies + +## Compilation Requirements + +To create dynamically linkable WebAssembly modules, you need: + +- **Clang 19** or later +- **WASIX sysroot with PIC support** (`WASIX_SYSROOT_PIC` environment variable) +- **wasm-ld-19** linker + +### Compiling Dynamic Libraries + +Create a shared library (`.so` file) with the following flags: + +```bash +# Compile to object file +clang-19 \ + --target=wasm32-wasi --sysroot=${WASIX_SYSROOT_PIC} \ + -matomics -mbulk-memory -mmutable-globals -pthread \ + -mthread-model posix -ftls-model=local-exec \ + -fno-trapping-math -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_SIGNAL \ + -D_WASI_EMULATED_PROCESS_CLOCKS \ + -g -flto -O0 \ + -fPIC -fvisibility=default \ + -c mylibrary.c -o mylibrary.o + +# Link to shared library +wasm-ld-19 \ + --extra-features=atomics,bulk-memory,mutable-globals \ + --export=__wasm_call_ctors --export-if-defined=__wasm_apply_data_relocs \ + --experimental-pic --unresolved-symbols=import-dynamic \ + -shared --shared-memory \ + -o libmylibrary.so mylibrary.o +``` + +### Compiling Main Executables + +For executables that use dynamic linking: + +```bash +# Compile main program +clang-19 \ + --target=wasm32-wasi --sysroot=${WASIX_SYSROOT_PIC} \ + -matomics -mbulk-memory -mmutable-globals -pthread \ + -mthread-model posix -ftls-model=local-exec \ + -fno-trapping-math -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_SIGNAL \ + -D_WASI_EMULATED_PROCESS_CLOCKS \ + -g -flto -O0 \ + -fPIC \ + -c main.c -o main.o + +# Link executable (can link against dynamic libraries) +wasm-ld-19 \ + -L. -lmylibrary \ + -L${WASIX_SYSROOT_PIC}/lib \ + -L${WASIX_SYSROOT_PIC}/lib/wasm32-wasi \ + --whole-archive --export-all \ + main.o \ + ${WASIX_SYSROOT_PIC}/lib/wasm32-wasi/crt1.o \ + -lc -lresolv -lrt -lm -lpthread \ + -lwasi-emulated-mman \ + --max-memory=4294967296 --import-memory --shared-memory \ + --extra-features=atomics,bulk-memory,mutable-globals \ + --export=__wasm_signal --export=__tls_size --export=__tls_align \ + --export=__tls_base --export=__wasm_call_ctors --export-if-defined=__wasm_apply_data_relocs \ + --experimental-pic \ + -pie \ + --no-export-dynamic \ + -o main.wasm +``` + +## Dynamic Linking API + +Wasmer provides three syscalls for dynamic linking, similar to POSIX: + +### dlopen + +Load a dynamic library: + +```c +#include + +void *handle = dlopen("libmylibrary.so", RTLD_NOW); +if (!handle) { + fprintf(stderr, "Failed to load library: %s\n", dlerror()); + return 1; +} +``` + +**Flags:** +- `RTLD_NOW`: Resolve all symbols immediately +- `RTLD_LAZY`: Resolve symbols as needed (currently same as RTLD_NOW) +- `RTLD_GLOBAL`: Make symbols available globally +- `RTLD_NOLOAD`: Don't load, just check if already loaded +- `RTLD_NODELETE`: Don't unload on dlclose +- `RTLD_DEEPBIND`: Use deep binding for symbol resolution + +### dlsym + +Resolve symbols from loaded libraries: + +```c +// Get a function pointer +int (*my_function)(int) = dlsym(handle, "my_function"); +if (!my_function) { + fprintf(stderr, "Failed to find symbol: %s\n", dlerror()); + return 1; +} + +// Get a global variable +int *my_variable = dlsym(handle, "my_variable"); +if (!my_variable) { + fprintf(stderr, "Failed to find variable: %s\n", dlerror()); + return 1; +} + +// Use RTLD_DEFAULT to search all loaded modules +int (*global_func)(void) = dlsym(RTLD_DEFAULT, "global_function"); +``` + +### dlclose + +Unload a dynamic library: + +```c +if (dlclose(handle) != 0) { + fprintf(stderr, "Failed to unload library: %s\n", dlerror()); + return 1; +} +``` + +### Error Handling + +Use `dlerror()` to get detailed error messages: + +```c +char *error = dlerror(); +if (error) { + fprintf(stderr, "Dynamic linking error: %s\n", error); +} +``` + +## Example: Simple Dynamic Library + +### Library Code (mylibrary.c) + +```c +#include + +// Exported global variable +int library_version = 42; + +// Exported function +int add_numbers(int a, int b) { + printf("Adding %d + %d in library\n", a, b); + return a + b; +} + +// Constructor (called when library loads) +void __attribute__((constructor)) library_init() { + printf("Library loaded!\n"); +} + +// Destructor (called when library unloads) +void __attribute__((destructor)) library_cleanup() { + printf("Library unloaded!\n"); +} +``` + +### Main Program (main.c) + +```c +#include +#include + +int main() { + // Load the library + void *handle = dlopen("libmylibrary.so", RTLD_NOW); + if (!handle) { + fprintf(stderr, "Failed to load library: %s\n", dlerror()); + return 1; + } + + // Get function pointer + int (*add_func)(int, int) = dlsym(handle, "add_numbers"); + if (!add_func) { + fprintf(stderr, "Failed to find function: %s\n", dlerror()); + dlclose(handle); + return 1; + } + + // Get global variable + int *version = dlsym(handle, "library_version"); + if (!version) { + fprintf(stderr, "Failed to find variable: %s\n", dlerror()); + dlclose(handle); + return 1; + } + + // Use the library + printf("Library version: %d\n", *version); + int result = add_func(10, 20); + printf("Result: %d\n", result); + + // Unload the library + dlclose(handle); + return 0; +} +``` + +## Threading Support + +Dynamic linking fully supports threading. Loaded modules can: +- Create and manage threads +- Share data across threads +- Use thread-local storage +- Synchronize with mutexes and other primitives + +```c +#include +#include + +void *thread_function(void *arg) { + void *handle = dlopen("libworker.so", RTLD_NOW); + void (*worker_func)(void) = dlsym(handle, "do_work"); + + if (worker_func) { + worker_func(); + } + + dlclose(handle); + return NULL; +} + +int main() { + pthread_t thread; + pthread_create(&thread, NULL, thread_function, NULL); + pthread_join(thread, NULL); + return 0; +} +``` + +## Running with Wasmer + +To run dynamically linked programs with Wasmer, ensure the shared libraries are accessible: + +```bash +# Place .so files in the same directory or use --mapdir +wasmer run main.wasm + +# Or mount a directory containing libraries +wasmer run --mapdir /lib:./libraries main.wasm +``` + +## Limitations and Considerations + +- **Experimental Feature**: Dynamic linking is currently experimental and may have limitations +- **Performance**: Dynamic linking adds runtime overhead compared to static linking +- **Memory Usage**: Each loaded module consumes additional memory +- **Compatibility**: Requires specific compilation flags and toolchain versions +- **Deep Sleep**: Dynamic linking is incompatible with deep sleep functionality + +## Best Practices + +1. **Error Handling**: Always check return values and use `dlerror()` for diagnostics +2. **Resource Management**: Match every `dlopen()` with a corresponding `dlclose()` +3. **Symbol Visibility**: Use appropriate visibility attributes for exported symbols +4. **Thread Safety**: Ensure thread-safe access to shared resources across modules +5. **Testing**: Thoroughly test dynamic loading scenarios and error conditions + +## Troubleshooting + +### Common Issues + +**Symbol not found**: Ensure the symbol is exported with proper visibility: +```c +__attribute__((visibility("default"))) int my_function(void); +``` + +**Library not found**: Check file paths and use `--mapdir` to mount directories: +```bash +wasmer run --mapdir /lib:./path/to/libraries main.wasm +``` + +**Compilation errors**: Verify you're using the correct clang version and flags: +```bash +clang-19 --version # Should be 19.x or later +``` + +**Threading issues**: Ensure all modules are compiled with threading support: +```bash +# Include these flags for all modules +-matomics -mbulk-memory -mmutable-globals -pthread +``` + +For more advanced usage and examples, see the [test cases](https://github.com/wasmerio/wasmer/tree/main/tests/c-wasi-tests) in the Wasmer repository. \ No newline at end of file diff --git a/pages/runtime/guides/dynamic-linking-c.mdx b/pages/runtime/guides/dynamic-linking-c.mdx new file mode 100644 index 0000000..e3293f4 --- /dev/null +++ b/pages/runtime/guides/dynamic-linking-c.mdx @@ -0,0 +1,476 @@ +# Dynamic Linking with C + +This guide walks through creating dynamically linked WebAssembly modules using C and Clang, demonstrating how to build shared libraries and executables that can load them at runtime. + +## Prerequisites + +Before starting, ensure you have: + +- **Clang 19** or later +- **wasm-ld-19** linker +- **WASIX sysroot with PIC support** (set `WASIX_SYSROOT_PIC` environment variable) + +```bash +# Verify your toolchain +clang-19 --version +wasm-ld-19 --version +echo $WASIX_SYSROOT_PIC +``` + +## Example: Math Library + +Let's create a simple math library that can be loaded dynamically. + +### Step 1: Create the Library (mathlib.c) + +```c +#include + +// Global variable that will be exported +int library_version = 100; + +// Private function (not exported) +static void internal_log(const char* operation, int a, int b, int result) { + printf("[MathLib] %s(%d, %d) = %d\n", operation, a, b, result); +} + +// Exported functions +int add(int a, int b) { + int result = a + b; + internal_log("add", a, b, result); + return result; +} + +int multiply(int a, int b) { + int result = a * b; + internal_log("multiply", a, b, result); + return result; +} + +int get_version(void) { + return library_version; +} + +// Constructor - called when library is loaded +void __attribute__((constructor)) mathlib_init() { + printf("MathLib v%d loaded!\n", library_version); +} + +// Destructor - called when library is unloaded +void __attribute__((destructor)) mathlib_cleanup() { + printf("MathLib unloaded!\n"); +} +``` + +### Step 2: Compile the Library + +```bash +# Compile to object file +clang-19 \ + --target=wasm32-wasi --sysroot=${WASIX_SYSROOT_PIC} \ + -matomics -mbulk-memory -mmutable-globals -pthread \ + -mthread-model posix -ftls-model=local-exec \ + -fno-trapping-math -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_SIGNAL \ + -D_WASI_EMULATED_PROCESS_CLOCKS \ + -g -flto -O0 \ + -fPIC -fvisibility=default \ + -c mathlib.c -o mathlib.o + +# Link to shared library +wasm-ld-19 \ + --extra-features=atomics,bulk-memory,mutable-globals \ + --export=__wasm_call_ctors --export-if-defined=__wasm_apply_data_relocs \ + --experimental-pic --unresolved-symbols=import-dynamic \ + -shared --shared-memory \ + -o libmathlib.so mathlib.o +``` + +### Step 3: Create the Main Program (calculator.c) + +```c +#include +#include +#include + +int main() { + printf("Calculator starting...\n"); + + // Load the math library + void *mathlib = dlopen("libmathlib.so", RTLD_NOW); + if (!mathlib) { + fprintf(stderr, "Failed to load mathlib: %s\n", dlerror()); + return 1; + } + + // Get function pointers + int (*add_func)(int, int) = dlsym(mathlib, "add"); + int (*multiply_func)(int, int) = dlsym(mathlib, "multiply"); + int (*get_version_func)(void) = dlsym(mathlib, "get_version"); + + if (!add_func || !multiply_func || !get_version_func) { + fprintf(stderr, "Failed to find functions: %s\n", dlerror()); + dlclose(mathlib); + return 1; + } + + // Get global variable + int *version_ptr = dlsym(mathlib, "library_version"); + if (!version_ptr) { + fprintf(stderr, "Failed to find version variable: %s\n", dlerror()); + dlclose(mathlib); + return 1; + } + + // Use the library + printf("Library version: %d (via function: %d)\n", + *version_ptr, get_version_func()); + + int a = 15, b = 25; + printf("Calculating %d + %d = %d\n", a, b, add_func(a, b)); + printf("Calculating %d * %d = %d\n", a, b, multiply_func(a, b)); + + // Try to access private function (should fail) + void *private_func = dlsym(mathlib, "internal_log"); + if (private_func) { + printf("ERROR: Private function should not be accessible!\n"); + } else { + printf("Good: Private function is not accessible\n"); + } + + // Unload the library + printf("Unloading library...\n"); + if (dlclose(mathlib) != 0) { + fprintf(stderr, "Failed to unload library: %s\n", dlerror()); + return 1; + } + + printf("Calculator finished!\n"); + return 0; +} +``` + +### Step 4: Compile the Main Program + +```bash +# Compile main program +clang-19 \ + --target=wasm32-wasi --sysroot=${WASIX_SYSROOT_PIC} \ + -matomics -mbulk-memory -mmutable-globals -pthread \ + -mthread-model posix -ftls-model=local-exec \ + -fno-trapping-math -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_SIGNAL \ + -D_WASI_EMULATED_PROCESS_CLOCKS \ + -g -flto -O0 \ + -fPIC \ + -c calculator.c -o calculator.o + +# Link executable +wasm-ld-19 \ + -L${WASIX_SYSROOT_PIC}/lib \ + -L${WASIX_SYSROOT_PIC}/lib/wasm32-wasi \ + --whole-archive --export-all \ + calculator.o \ + ${WASIX_SYSROOT_PIC}/lib/wasm32-wasi/crt1.o \ + -lc -lresolv -lrt -lm -lpthread \ + -lwasi-emulated-mman \ + --max-memory=4294967296 --import-memory --shared-memory \ + --extra-features=atomics,bulk-memory,mutable-globals \ + --export=__wasm_signal --export=__tls_size --export=__tls_align \ + --export=__tls_base --export=__wasm_call_ctors --export-if-defined=__wasm_apply_data_relocs \ + --experimental-pic \ + -pie \ + --no-export-dynamic \ + -o calculator.wasm +``` + +### Step 5: Run with Wasmer + +```bash +# Run the calculator (library must be in same directory or mounted) +wasmer run calculator.wasm + +# Or mount a directory containing the library +wasmer run --mapdir /lib:. calculator.wasm +``` + +Expected output: +``` +Calculator starting... +MathLib v100 loaded! +Library version: 100 (via function: 100) +[MathLib] add(15, 25) = 40 +Calculating 15 + 25 = 40 +[MathLib] multiply(15, 25) = 375 +Calculating 15 * 25 = 375 +Good: Private function is not accessible +Unloading library... +MathLib unloaded! +Calculator finished! +``` + +## Advanced Example: Plugin System + +Here's a more complex example showing a plugin system with multiple libraries. + +### Plugin Interface (plugin.h) + +```c +#ifndef PLUGIN_H +#define PLUGIN_H + +typedef struct { + const char* name; + const char* version; + int (*process)(int input); + void (*cleanup)(void); +} plugin_interface_t; + +// Function that each plugin must export +plugin_interface_t* get_plugin_interface(void); + +#endif +``` + +### Plugin Implementation (doubler_plugin.c) + +```c +#include +#include "plugin.h" + +static int doubler_process(int input) { + printf("Doubler plugin: %d -> %d\n", input, input * 2); + return input * 2; +} + +static void doubler_cleanup(void) { + printf("Doubler plugin cleanup\n"); +} + +static plugin_interface_t doubler_interface = { + .name = "Doubler", + .version = "1.0", + .process = doubler_process, + .cleanup = doubler_cleanup +}; + +plugin_interface_t* get_plugin_interface(void) { + return &doubler_interface; +} + +void __attribute__((constructor)) plugin_init() { + printf("Doubler plugin loaded\n"); +} +``` + +### Plugin Host (plugin_host.c) + +```c +#include +#include +#include +#include "plugin.h" + +typedef struct { + void* handle; + plugin_interface_t* interface; +} loaded_plugin_t; + +int main() { + const char* plugin_files[] = { + "libdoubler.so", + "libsquarer.so", // You can create this as an exercise + NULL + }; + + loaded_plugin_t plugins[10]; + int plugin_count = 0; + + // Load all plugins + for (int i = 0; plugin_files[i] != NULL; i++) { + void* handle = dlopen(plugin_files[i], RTLD_NOW); + if (!handle) { + printf("Warning: Could not load %s: %s\n", + plugin_files[i], dlerror()); + continue; + } + + plugin_interface_t* (*get_interface)(void) = + dlsym(handle, "get_plugin_interface"); + + if (!get_interface) { + printf("Warning: %s does not export get_plugin_interface\n", + plugin_files[i]); + dlclose(handle); + continue; + } + + plugin_interface_t* interface = get_interface(); + if (!interface) { + printf("Warning: %s returned NULL interface\n", plugin_files[i]); + dlclose(handle); + continue; + } + + plugins[plugin_count].handle = handle; + plugins[plugin_count].interface = interface; + plugin_count++; + + printf("Loaded plugin: %s v%s\n", + interface->name, interface->version); + } + + // Process data through all plugins + int data = 5; + printf("\nProcessing data: %d\n", data); + + for (int i = 0; i < plugin_count; i++) { + data = plugins[i].interface->process(data); + } + + printf("Final result: %d\n", data); + + // Cleanup and unload plugins + printf("\nUnloading plugins...\n"); + for (int i = 0; i < plugin_count; i++) { + if (plugins[i].interface->cleanup) { + plugins[i].interface->cleanup(); + } + dlclose(plugins[i].handle); + } + + return 0; +} +``` + +## Threading with Dynamic Libraries + +Dynamic libraries fully support threading. Here's an example: + +### Thread-Safe Library (threaded_counter.c) + +```c +#include +#include + +static int counter = 0; +static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER; + +int increment_counter(void) { + pthread_mutex_lock(&counter_mutex); + int new_value = ++counter; + printf("Counter incremented to: %d (thread: %p)\n", + new_value, (void*)pthread_self()); + pthread_mutex_unlock(&counter_mutex); + return new_value; +} + +int get_counter(void) { + pthread_mutex_lock(&counter_mutex); + int value = counter; + pthread_mutex_unlock(&counter_mutex); + return value; +} + +void reset_counter(void) { + pthread_mutex_lock(&counter_mutex); + counter = 0; + printf("Counter reset\n"); + pthread_mutex_unlock(&counter_mutex); +} +``` + +### Multi-threaded Main Program + +```c +#include +#include +#include + +static void* counter_handle = NULL; +static int (*increment_func)(void) = NULL; + +void* worker_thread(void* arg) { + int thread_id = *(int*)arg; + + for (int i = 0; i < 5; i++) { + int value = increment_func(); + printf("Thread %d: incremented to %d\n", thread_id, value); + } + + return NULL; +} + +int main() { + // Load counter library + counter_handle = dlopen("libthreaded_counter.so", RTLD_NOW); + if (!counter_handle) { + fprintf(stderr, "Failed to load counter library: %s\n", dlerror()); + return 1; + } + + increment_func = dlsym(counter_handle, "increment_counter"); + int (*get_func)(void) = dlsym(counter_handle, "get_counter"); + + if (!increment_func || !get_func) { + fprintf(stderr, "Failed to find functions: %s\n", dlerror()); + return 1; + } + + // Create multiple threads + pthread_t threads[3]; + int thread_ids[3] = {1, 2, 3}; + + for (int i = 0; i < 3; i++) { + pthread_create(&threads[i], NULL, worker_thread, &thread_ids[i]); + } + + // Wait for all threads + for (int i = 0; i < 3; i++) { + pthread_join(threads[i], NULL); + } + + printf("Final counter value: %d\n", get_func()); + + dlclose(counter_handle); + return 0; +} +``` + +## Best Practices + +1. **Error Handling**: Always check return values from `dlopen`, `dlsym`, and `dlclose` +2. **Symbol Visibility**: Use `__attribute__((visibility("default")))` for exported symbols +3. **Thread Safety**: Use proper synchronization when sharing data across threads +4. **Resource Management**: Match every `dlopen` with `dlclose` +5. **Interface Design**: Define clear interfaces between main program and plugins + +## Common Issues + +### Symbol Not Found +```c +// Wrong: function not exported +static int my_function(void) { return 42; } + +// Correct: function exported with default visibility +__attribute__((visibility("default"))) int my_function(void) { return 42; } +``` + +### Library Not Found +```bash +# Make sure library is in the right location +ls -la *.so + +# Or use --mapdir to mount the directory +wasmer run --mapdir /lib:./my_libraries main.wasm +``` + +### Compilation Errors +```bash +# Ensure you're using the right clang version +clang-19 --version + +# Check that WASIX_SYSROOT_PIC is set correctly +echo $WASIX_SYSROOT_PIC +ls $WASIX_SYSROOT_PIC/lib/wasm32-wasi/ +``` + +This guide provides a solid foundation for using dynamic linking with C in Wasmer. The examples demonstrate both simple library loading and more complex plugin architectures with threading support. \ No newline at end of file