ARTICLE
Dynamic Linking: a crash course
From WebAssembly in Action by C. Gerard Gallant
This article covers
§ How dynamic linking works for WebAssembly modules
§ Why you might want to use dynamic linking and why you might not
§ How to create WebAssembly modules as main or side modules
§ What the different options are for dynamic linking and how to use each approach
__________________________________________________________________
Take 37% off WebAssembly in Action. Just enter fccgallant into the discount code box at checkout at manning.com.
__________________________________________________________________
Source code for this article can be downloaded from the book’s Product page.
When it comes to WebAssembly modules, dynamic linking is the process of joining two or more modules together at runtime where the unresolved symbols from one module (functions for example) resolve to symbols existing in another module. You’ll still have the original number of WebAssembly modules but now they’re linked together and able to access each other’s functionality, as shown in figure 1.
You can dynamically link in several different ways to implement WebAssembly modules, making this a large topic.
Dynamic linking: pros and cons
Why would you want to use dynamic linking instead of the single WebAssembly module approach?
You might consider using dynamic linking for several reasons, including the following:
- To speed up development time. Rather than compiling one big module, you compile only the modules which changed.
- The core of your application can be separated out for easier sharing. Rather than having two or three big WebAssembly modules with the same logic in each, you can have a core module and several smaller modules which link to the core module. An example of this approach is a game engine where the engine can be downloaded separately from the game. Multiple games could share the same engine.
- The smaller something is, the faster it downloads, and downloading only what you need initially speeds up load time. As additional logic is needed by the webpage, a smaller module with logic specific to that area can be downloaded.
- If a portion of your logic is never used, it’s never downloaded because logic is downloaded only as needed. The result is that you won’t waste time downloading and processing something up front if it isn’t needed.
- The browser caches the module similar to how it caches images or JavaScript files. Only the modules which change are downloaded again making subsequent page views faster because only a portion of the logic needs to be redownloaded.
Although dynamic linking has a number of advantages, it isn’t the best choice for every situation; it’s best to test to see if it’s right for your needs.
Dynamic linking can result in some performance impacts. According to Emscripten’s documentation, the performance hit could be five to ten percent or higher depending on how your code is structured.
Some of the areas where you could see a performance impact include the following:
- In development the build configuration becomes a bit more complicated because you now need to create two or more WebAssembly modules rather than one.
- Rather than having one WebAssembly module to download, you’ll now have at least two modules initially, which means you’ll also have more network requests.
- The modules need to be linked together, and there’s a bit more processing involved during instantiation
- The browser vendors are working on improving performance for various types of calls but, according to Emscripten, the function calls between linked modules can be slower than function calls within the module. If you have a lot of calls between the linked modules, you may see performance issues.
Now that you know the pros and cons of dynamic linking, let’s look at the different ways that dynamic linking can be implemented with WebAssembly modules.
Dynamic linking options
Three options are available for dynamic linking when using Emscripten:
- Your C or C++ code can manually link to a module by using the
dlopen
function. - You can instruct Emscripten that there are WebAssembly modules to link to by specifying them in the
dynamicLibraries
array of Emscripten’s generated JavaScript file. When Emscripten instantiates the WebAssembly module, it automatically downloads and links modules which are specified in this array. - In your JavaScript you can manually take the exports of one module and pass them in as the imports to another module using the WebAssembly JavaScript API.
INFO: The following website has a good overview of the API: https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API
Before you learn how to use each dynamic linking technique, let’s take a look at what the differences are between side modules and main modules.
Side modules and main modules
It’s possible to creat WebAssembly modules as side modules to avoid generating an Emscripten JavaScript file. This allows you to manually download and instantiate the WebAssembly modules using the WebAssembly JavaScript API. Although creating a side module to manually use the WebAssembly JavaScript API is a useful side-effect to aid in learning how things work under the hood, side modules are intended for dynamic linking.
With side modules, Emscripten omits the standard C library functions and the JavaScript file because the side modules are linked to a main module at runtime (figure 7.2). The main module has the Emscripten-generated JavaScript file and standard C library functions and, when linked, the side module gains access to the main module’s features.
Side modules are created by including the SIDE_MODULE
flag as part of the command line to instruct Emscripten to not generate the JavaScript file or include any standard C library functions in the module.
Main modules are created in a fashion similar to how you create a side module but you use the MAIN_MODULE
flag as part of the command line instead. The flag tells the Emscripten compiler to include system libraries and logic needed for dynamic linking. As shown in figure 3, the main module has the Emscripten-generated JavaScript file as well as the standard C library functions.
NOTE: One thing to be aware of with dynamic linking is the multiple side modules linked to a main module but there can be only one main module. Also, being a main module has nothing to do with the main() function. The main function can be placed in any of the modules including one of the side modules.
The first type of dynamic linking you’ll learn is the dlopen
approach.
Dynamic linking: dlopen
Suppose your boss has asked you to create a WebAssembly module and one of the things that it needs to do is determine the prime numbers that exist between a certain range of numbers.
You’d rather not copy and paste the logic into this new WebAssembly module because you don’t want to maintain two identical sets of code; if there’s an issue discovered in the code, you need to modify the same logic in two places, which could lead to one spot being missed if a developer isn’t aware of the second spot or one of the locations is modified incorrectly.
Instead of duplicating the code, what you’d like to do is modify the existing calculate_primes code to be used as both a normal WebAssembly module and also be callable from your new WebAssembly module.
As shown in figure 4, the steps for this scenario are the following:
- Modify the calculate_primes.c file to also be called by the main module. You’ll rename the file to calculate_primes.cpp.
- Use Emscripten to generate the WebAssembly file, from the calculate_primes.cpp file, as a side module
- Create the logic (main.cpp) that links to the side module using a call to the dlopen function
- Use Emscripten to generate the WebAssembly file, from the main.cpp file, as a main module and to also generate the HTML template file
For this scenario, you’re going to call the dlopen
function from your C++ code to link to the calculate_primes side module. To open the side module dlopen needs the WebAssembly file to be in Emscripten’s file system.
The trick with a file system is that a WebAssembly module is running in a virtual machine and doesn’t have access to the file system of the device. To get around this, Emscripten provides the WebAssembly module with one of several different types of file system depending on where the module is running (in a browser or in Node.js, for example) and how persistent the storage needs to be. By default, Emscripten’s file system is in memory, and any data written to it is lost when the webpage is refreshed.
Emscripten’s file system is accessed through the FS
object in Emscripten’s generated JavaScript file but that object is included only if your WebAssembly module’s code accesses files. To learn more about Emscripten’s file system, visit the following website: https://emscripten.org/docs/api_reference/Filesystem-API.html
In this article, you’ll only learn how to use the emscripten_async_wget
function which allows you to download a WebAssembly module to Emscripten’s file system to open it with the dlopen function.
When using the dlopen approach to dynamic linking, it’s possible for your module to call the main function in the calculate_primes module, even if your module also has a main function. Being able to call the main function of a side module might be useful if the module is from a third party and contains initialization logic. Being able to call a main function in another module is possible because dlopen returns a handle to the side module and you then get a reference to the function which you want to call based on that handle.
TIP: This is one advantage to using the dlopen approach of dynamic linking, compared with using the dynamicLibraries approach that you’ll learn about in the next section. When it comes to using the dynamicLibraries approach, calling a function in another module when you have a function with the same name in your module won’t work. You’ll end up calling the function in your module, which could result in a recursive function call.
The first step of the process (figure 5) to implement dynamic linking is to modify the calculate_primes.cpp file, allowing it to be compiled into a side module.
Modify the calculate_primes.cpp file
In your WebAssembly\
folder, create a folder named Chapter 7\7.2.2 dlopen\source\
for the files that you’ll use in this section.
Copy the calculate_primes.c
file from your Chapter 3\3.5 js_plumbing\source\
folder to your newly created source\
folder and rename the file extension to cpp
. Open the calculate_primes.cpp
file with your favorite editor.
Replace the stdlib.h
header file with cstdlib
, the stdio.h
header file with cstdio
, and then add the extern "C"
opening block between the emscripten.h
header file and before the IsPrime
function. The beginning of your calculate_primes.cpp file should now look like the code in the following snippet:
#include <cstdlib> #A
#include <cstdio> #B
#include <emscripten.h>
#ifdef __cplusplus #C
extern "C" {
#endif
#A Replace the stdlib.h header
#B Replace the stdio.h header
#C Add the opening extern "C" block
In the calculate_primes.cpp
file, after the IsPrime
function and before the main
function, create a function called FindPrimes
that returns void
and accepts two integer
parameters (start
and end
) for the start and end range of the prime number search.
Delete the start and end variable declaration lines of code from the main
function and then move the remaining code, except for the return 0
line, from the main
function into the FindPrimes
function.
Add the EMSCRIPTEN_KEEPALIVE
declaration above the FindPrimes function such that the function is automatically added to the list of exported functions when you compile. Doing this simplifies things when you use Emscripten to generate the WebAssembly module because you don’t have to explicitly specify the function at the command line.
Modify the main
function to call the new FindPrimes
function and pass in the original range of 3
and 100000
.
Finally, after the main function, add the closing bracket for the extern "C"
block.
Your new FindPrimes function, the modified main function, and the closing bracket for the extern “C” block should now look like the code in listing 1.
Listing 1 The new FindPrimes function and the modified main function
…
EMSCRIPTEN_KEEPALIVE
void FindPrimes(int start, int end) { #A
printf("Prime numbers between %d and %d:\n", start, end);
for (int i = start; i <= end; i += 2) {
if (IsPrime(i)) {
printf("%d ", i);
}
}
printf("\n");
}
int main() {
FindPrimes(3, 100000); #B
return 0;
}
#ifdef __cplusplus #C
}
#endif
#A New function which is now exported and callable by other modules
#B Display the original range of prime numbers
#C Add the closing bracket for the extern “C” block
Now that you’ve modified the C code to be called by other modules, it’s time to move to step 2 (figure 6) and compile the code into a WebAssembly side module.
Use Emscripten to generate the WebAssembly file as a side module from calculate_primes.cpp
When you created WebAssembly side modules you replace the standard C library functions with some replacement code. You do this to keep the side module working, even though the standard C library functions aren’t available.
You don’t need the replacement code in this case because the side module is linked to the main module at runtime and the main module has the standard C library functions.
To compile the modified calculate_primes.cpp
file as a WebAssembly side module, open a command prompt, navigate to the Chapter 7\7.2.2 dlopen\source\
folder, and then run the following command:
emcc calculate_primes.cpp -s SIDE_MODULE=2 -O1
[CA] -o calculate_primes.wasm
Now that you’ve created the side module, the next step (figure 7) is to create the main module.
Create the logic that links to the side module
In your Chapter 7\7.2.2 dlopen\source\
folder, create a file named main.cpp
and then open it in your favorite editor.
The first thing that you need to add to the main.cpp
file are the includes for the header files. In this case, you’ll want to include the dlfcn.h
header file, along with cstdlib and emscripten.h, because it has the declarations related to dynamic linking when using dlopen
. Then you need to add the extern "C"
block.
The code in your main.cpp file should now look like the code in listing 2.
Listing 2 The main.cpp file with the header file includes and extern “C” block
#include <cstdlib>
#ifdef __EMSCRIPTEN__
#include <dlfcn.h> #A
#include <emscripten.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#B
#ifdef __cplusplus
}
#endif
#A The header file needed for dlopen-related logic
#B Your module’s code is placed here
In the code you’re about to write, you’ll be using the dlopen
function to get a handle to a WebAssembly side module. Once you have that handle, you’ll use the dlsym
function to get a function pointer to the desired function in that module. To simplify the code when you call the dlsym function, the next thing that you’ll need to do is define the function signature for the FindPrimes
function that you’ll be calling in the side module.
The FindPrimes function returns void and has two integer parameters. The function pointer signature for the FindPrimes function is shown in the following snippet, which you need to include in the main.cpp file within the extern "C"
block:
typedef void(*FindPrimes)(int,int);
You’ll now add a main
function to your file to allow the Emscripten compiler to add the function to the start
section of the WebAssembly module. This causes the main function to run automatically once the module has been instantiated.
In your main function, you’ll add a call to the emscripten_async_wget
function to download the side module to Emscripten’s file system. This call is asynchronous and calls a callback function, which you specify once the download is complete. The parameters that you’ll pass to the emscripten_async_wget function, and their order, are the following:
- The file to download:
"calculate_primes.wasm"
- The name to give to the file when it gets added to Emscripten’s file system. In this case, it’s given the same name it already has.
- A callback function if the download is successful:
CalculatePrimes
- You’ll leave the fourth parameter
NULL
in this case because you won’t specify a callback function. If you wanted to, you could specify a callback function in the event there was an error downloading the file.
Following the FindPrimes function pointer signature in your main.cpp file, and within the extern "C"
block, add the code from the following snippet:
int main() {
emscripten_async_wget("calculate_primes.wasm", #A
"calculate_primes.wasm", #B
CalculatePrimes, #C
NULL); #D
return 0;
}
#A The file to download
#B The name to give to the file in Emscripten’s file system
#C The callback function on success
#D The callback function on error
The last thing you’ll need to add to the main.cpp file is a function which holds the logic to open the side module, gets a reference to the FindPrimes function, and then calls the FindPrimes function.
When the emscripten_async_wget function finishes downloading the calculate_primes WebAssembly module, it calls the CalculatePrimes function which you specified and it passes in a parameter indicating the loaded file name.
To open the side module, use the dlopen
function, passing in two parameter values:
- The file name to open from the file name parameter the CalculatePrimes function receives
- An integer indicating the mode:
RTLD_NOW
MODE: When an executable file is brought into the address space of a process it might have references to symbols which aren’t known until the file is loaded. The references for the symbols need to be relocated before the symbols can be accessed. The mode value is used to tell dlopen when the relocation should happen. The RTLD_NOW value is asking dlopen for the relocations to happen when the file is loaded. More information about dlopen and the Mode flags can be found at http://pubs.opengroup.org/onlinepubs/9699919799/
The dlopen function call returns a handle to the file, as shown in the following code snippet:
void* handle = dlopen(file_name, RTLD_NOW);
Once you have a handle to the side module, you call the dlsym
function passing in the following parameter values to get a reference to the function you want to call:
- The handle of the side module
- The name of the function you want a reference to:
"FindPrimes"
The dlsym function returns a function pointer to the requested function as shown in the following code snippet:
FindPrimes find_primes = (FindPrimes)dlsym(handle, "FindPrimes");
Once you have a function pointer, you can call it the same as you’d call a normal function.
When you’ve finished with a linked module, you can release it by passing the file’s handle to the dlclose
function.
Pulling everything together, your CalculatePrimes
function should look like the code in listing 3. Add the code in listing 3 to your main.cpp file between the FindPrimes function pointer signature and the main
function.
Listing 3 The CalculatePrimes function that calls a function in the side module
…
void CalculatePrimes(const char* file_name) {
void* handle = dlopen(file_name, RTLD_NOW); #A
if (handle == NULL) { return; }
FindPrimes find_primes =
(FindPrimes)dlsym(handle, "FindPrimes"); #B
if (find_primes == NULL) { return; }
find_primes(3, 100000); #C
dlclose(handle); #D
}
…
#A Open the side module
#B Get a reference to the FindPrimes function
#C Call the function in the side module
#D Close the side module
Now that you’ve created the code for your main module, you can move on to the final step (figure 8) and compile it into a WebAssembly module. You’ll also use Emscripten to generate the HTML template file.
Use Emscripten to generate the WebAssembly file as a main module from main.cpp
Rather than creating an HTML page to view the results, make use of Emscripten’s HTML template by specifying the output file with an .html
extension.
To compile your main.cpp file into a main module, you need to include the -s MAIN_MODULE=1
flag. Unfortunately, if you view the generated HTML page using only the following command line, you’ll see the error shown in figure 9:
emcc main.cpp -s MAIN_MODULE=1 -o main.html
You can see that the WebAssembly module was loaded and dlopen linked to the side module without issue because the text ‘Prime numbers between 3 and 100000
’ is written by the FindPrimes function in the side module. If there was an issue with the dynamic linking, your code wouldn’t have reached that point. Because none of the prime numbers have been written to the screen, it suggests that the issue is in the FindPrimes function of your side module but after the printf call to indicate the range.
It turns out that the issue is with the calculate_primes.cpp file’s use of the printf function when passing in only one character. In this case, the linefeed character (\n
) at the end of the FindPrimes function is causing the error. The printf function makes use of a putchar function under the hood and that function isn’t being included by default.
Three options are available to correct this error:
- Include the
_putchar
function in theEXPORTED_FUNCTIONS
array as part of the command line when generating the WebAssembly module. When testing this as a possible fix, including that function alone causes the error to go away but, unfortunately, nothing is displayed on the webpage. If you use this approach, you’ll need to include the_main
function of the module in the array too. - You could modify the printf call in the calculate_primes.cpp file causing it to output at least two characters to prevent the printf call from using the putchar function internally. The problem with using this approach is that if a printf of one character is used anywhere else, the error happens again. Because of that possibility, this isn’t a recommended fix.
- You could include the
-s EXPORT_ALL=1
flag to force Emscripten to include all the symbols when it generates the WebAssembly module and JavaScript file. This works but it isn’t recommended unless there are no other workarounds because, in this case, it results in a doubling of generated JavaScript file’s size to have one function exported.
Unfortunately, all three approaches feel like a hack. The first approach appears to be the best option available and, to correct the error, you’ll use the EXPORTED_FUNCTIONS
command line array to have the _putchar
and _main
functions exported by the module.
To compile the main.cpp
file into a WebAssembly main module, open a command prompt, navigate to the Chapter 7\7.2.2 dlopen\source\
folder, and then run the following command:
emcc main.cpp -s MAIN_MODULE=1
[CA] -s EXPORTED_FUNCTIONS=['_putchar','_main'] -o main.html
Now that your WebAssembly modules have been created, you can view the results.
Viewing the results
You can open your browser and type http://localhost:8080/main.html
into the address box to see the generated webpage.
As shown in figure 10, the webpage should display the list of prime numbers in both the textbox and in the console window of the browser’s developer tools. The prime numbers are determined by the side module, and it calls the printf function which is part of the main module.
You may notice that there’s a JavaScript warning about the addFunction function in Emscripten’s generated JavaScript file. This is due to the dlsym function calling the addFunction function but not specifying the second parameter. The warning can be ignored because the second parameter isn’t used by the addFunction function.
Now that you’ve learned how to do dynamic linking by using dlopen, you’ll learn how to use the dynamicLibraries approach.
Dynamic linking: dynamicLibraries
Imagine that your coworkers and boss have seen your new WebAssembly modules in action. They’re quite impressed with what you’ve done with dlopen but your boss has read up on dynamic linking as you were building the modules and discovered that you can also implement dynamic linking using Emscripten’s dynamicLibraries
array.
Your boss is curious to know how the dynamicLibraries approach compares with dlopen, and you’ve been asked to leave the calculate_primes side module as it is but to create a main module that links to it using dynamicLibraries.
As shown in figure 11, the steps for this scenario are the following:
- Create the logic (main.cpp) that talks to the side module
- Create a JavaScript file which is included in Emscripten’s generated JavaScript file to instruct Emscripten about the side module you want it to link to.
- Use Emscripten to generate the WebAssembly file, from the main.cpp file, as a main module and to also generate the HTML template file
Create the logic that talks to the side module
The first step of the process (figure 12) for this scenario is to create the main.cpp file which holds that logic which talks to the side module.
In your Chapter 7\
folder, create a 7.2.3 dynamicLibraries\source\
folder. In your new source
folder
- Copy in the
calculate_primes.wasm
file from your7.2.2 dlopen\source\
folder - Create a
main.cpp
file and then open it with your favorite editor
Add the header files for the standard C library and Emscripten. Then add the extern “C” block. The code in your main.cpp file should now look like the code in listing 4.
Listing 4 The main.cpp file with the header file includes and extern “C” block
#include <cstdlib>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#A
#ifdef __cplusplus
}
#endif
#A Your module’s code is placed here
In a moment you’ll write a main function that calls the FindPrimes
function in the calculate_primes side module. Because the FindPrimes function is part of a different module, you need to include its function signature, prefixed with the extern
keyword, causing the compiler to know that the function is available once the code is run.
Add the following function signature within the extern "C"
block in the main.cpp file:
extern void FindPrimes(int start, int end);
The last thing you need to do in the main.cpp file is to add the main
function to run the code automatically once the WebAssembly module is instantiated. In the main function, call the FindPrimes
function passing in the number range of 3
to 99
.
Add the following snippet to your main.cpp file within the extern "C"
block but after the FindPrimes
function signature:
int main() {
FindPrimes(3, 99);
return 0;
}
Your C++ code is now ready to be turned into a WebAssembly module but, before you use Emscripten to do that, you need to create the JavaScript code which instructs Emscripten to link to your side module (figure 13).
Create JavaScript to instruct Emscripten about the side module you want it to link to
Because your boss wants to know what the differences are between the dlopen and dynamicLibraries approaches, create the WebAssembly module and have Emscripten generate the HTML template to run for you rather than creating an HTML webpage of your own.
To link a side module to your main module using the dynamicLibraries approach, you need to write some JavaScript to specify the side module that Emscripten needs to link to. To do this, you specify the side module file names in Emscripten’s dynamicLibraries array before Emscripten instantiates the module.
When using Emscripten’s HTML template, you can include JavaScript near the beginning of Emscripten’s generated JavaScript file by specifying a JavaScript file in the command line using the --pre-js
flag when creating the WebAssembly module.
If you were building your own webpage, you could specify settings, like the dynamicLibraries array, in a Module
object before the HTML page’s script tag for Emscripten’s generated JavaScript file. When Emscripten’s JavaScript file loads, it creates its own Module object but, if there’s an existing Module object, it copies the values from that object into the new Module object.
MORE INFO: A number of settings can be adjusted to control the execution of Emscripten’s generated JavaScript code. The following webpage lists some of the settings that can be adjusted: https://emscripten.org/docs/api_reference/module.html
If you’re using the Emscripten-generated HTML template, the template specifies a Module object to respond to certain things. For example, it handles the printf calls;they’re displayed in the textbox on the webpage and in the browser’s console window rather than in the console window.
It’s important not to specify your own Module object when using the HTML template because, if you do, you’ll remove all of the template’s settings. When using the HTML template, any values you want to set need to be set directly on the Module object rather than creating a new object.
In your Chapter 7\7.2.3 dynamicLibraries\source\
folder create a file named pre.js
and then open it with your favorite editor.
You’ll need to add an array, containing the name of the side module you want linked, to the dynamicLibraries
property of the Module
object. Add the following snippet to your pre.js
file:
Module['dynamicLibraries'] = ['calculate_primes.wasm'];
Now that the JavaScript has been written, you can move to the final step of the process (figure 14) and have Emscripten generate the WebAssembly module.
Use Emscripten to generate the WebAssembly file as a main module from main.cpp
When you use Emscripten to generate your WebAssembly module you’ll want it to include the pre.js file’s contents in the generated JavaScript file. To have Emscripten include the file, you’ll need to specify the file using the –pre-js command line flag.
TIP: The name pre.js file name is used here as a naming convention because it’s passed to the Emscripten compiler via the –pre-js flag. You don’t have to use this naming convention but it makes it easier to understand the file’s purpose when you see it in your file system.
To generate your WebAssembly module as a main module, open a command prompt, navigate to the Chapter 7\7.2.3 dynamicLibraries\source\
folder, and then run the following command:
emcc main.cpp -s MAIN_MODULE=1 --pre-js pre.js
[CA] -s EXPORTED_FUNCTIONS=['_putchar','_main'] -o main.html
Now that you have your WebAssembly main module created, you can view the results.
Viewing the results
To view your new WebAssembly module in action, open your browser and type http://localhost:8080/main.html
into the address box to see the generated webpage as shown in figure 15.
Imagine that, as you were finishing up the WebAssembly module that was using the dynamicLibraries approach, you started to wonder if your boss might also want to see how manual dynamic linking might work.
Dynamic linking: WebAssembly JavaScript API
With dlopen, you need to download the side module but, after that, the dlopen function handles linking the side module for you. With dynamicLibraries, Emscripten handles downloading and instantiating the modules for you. With this approach, you’ll need to write the JavaScript code to download and instantiate the modules yourself using the WebAssembly JavaScript API.
For this scenario, you’ve decided to take the calculate_primes.c file and split it in two, where one WebAssembly module holds the IsPrime function and the other WebAssembly module has the FindPrimes function.
Because you’ll want to use the WebAssembly JavaScript API, both WebAssembly modules need to be compiled as side modules, which means neither has access to the standard C library functions. Without the standard C library available, you’ll need to replace the printf calls with a call to your own JavaScript function to log the prime numbers to the console window of the browser.
As shown in figure 16, the steps for this scenario are the following:
- Split the logic in the
calculate_primes.c
file into two files:is_prime.c
andfind_primes.c
- Use Emscripten to generate the WebAssembly side modules from the is_prime.c and find_primes.c files
- Copy the generated WebAssembly files to the server for use by the browser
- Create the HTML and JavaScript files needed to download, link, and interact with the two WebAssembly modules using the WebAssembly JavaScript API
Split the logic in the calculate_primes.c file into two files
As shown in figure 17, the first thing you’ll need to do is make a copy of the calculate_primes.c file to adjust the logic and split the file in two.
In your Chapter 7\
folder, create a 7.2.4 ManualLinking\source\
folder:
- Copy the
calculate_primes.cpp
file from yourChapter 7\7.2.2 dlopen\source\
folder to your newsource\
folder. Rename the calculate_primes.cpp file which you copied tois_prime.c
. - Make a copy of is_prime.c file and call it
find_primes.c
Open the is_prime.c
file with your favorite editor and then delete the following items:
- The
cstdlib
andcstdio
header files - The opening
extern "C"
block and the closing curly brace at the end of the file - The
FindPrimes
andmain
functions, leaving IsPrime the only function left in the file.
Add the EMSCRIPTEN_KEEPALIVE
declaration above the IsPrime
function to allow the IsPrime function to be included in the exported functions of the module.
Open the find_primes.c
file with your favorite editor and delete the following items:
- The
cstdlib
andcstdio
header file - The
extern "C"
block and the closing curly brace at the end of the file - The
IsPrime
andmain
functions, leaving FindPrimes the only function left in the file.
The FindPrimes function is calling the IsPrime function which is in the is_prime module. Because the function exists in another module, you need to include the function signature for the IsPrime function, preceded by the extern
keyword, such that the Emscripten compiler knows that the function is available when the code is run.
Add the following snippet before the FindPrimes function in your find_primes.c file:
extern int IsPrime(int value);
In a moment you’ll modify the FindPrimes function to call a function in your JavaScript code called LogPrime rather than calling the printf function. Because this function is also external to the module, you need to include a function signature for it too.
Add the following snippet before the IsPrime
function signature in your find_primes.c file:
extern void LogPrime(int prime);
Finally, the last thing you need to modify in the find_primes.c file is the FindPrimes function, preventing it from making calls to the printf function.
Delete the printf
calls from the beginning and end of the function and replace the printf call which is within the IsPrime if statement with a call to the LogPrime
function but don’t include the string. Pass in only the variable i to the LogPrime function.
The modified FindPrimes function should now look like the following snippet in your find_primes.c file:
EMSCRIPTEN_KEEPALIVE
void FindPrimes(int start, int end) {
for (int i = start; i <= end; i += 2) {
if (IsPrime(i)) {
LogPrime(i); #A
}
}
}
#A printf is replaced with a call to LogPrime
Now that your C code has been created, you can move on to step two (figure 18) which is to use Emscripten to compile the code into WebAssembly side modules.
Use Emscripten to generate the WebAssembly side modules
To generate your WebAssembly module from the is_prime.c file,
open a command prompt, navigate to the 7.2.4 ManualLinking\source\
folder, and then run the following command:
emcc is_prime.c -s SIDE_MODULE=2 -O1 -o is_prime.wasm
To generate your WebAssembly module from the find_primes.c file,
run the following command:
emcc find_primes.c -s SIDE_MODULE=2 -O1 -o find_primes.wasm
Now that you’ve created your two WebAssembly modules, the next steps are to create the webpage and JavaScript files that load, link and interact with the modules (figure 19).
Create the HTML and JavaScript files
In your Chapter 7\7.2.4 ManualLinking\
folder, create a frontend\
folder.
- Copy the
is_prime.wasm
andfind_primes.wasm
files from your7.2.4 ManualLinking\source\ folder to your new frontend\ folder
- Create a
main.html
file in yourfrontend\
folder and then open it with your favorite editor.
The HTML file is a basic webpage. It has some text to tell you how the page has loaded and then a script tag to load in the JavaScript file (main.js
) that handles loading and linking the two side modules together.
Add the contents of listing 5 to your main.html
file.
Listing 5 The contents of the main.html file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
HTML page I created for my WebAssembly module.
<script src="main.js"></script>
</body>
</html>
Your next step is to create the JavaScript file which handles downloading and linking the two WebAssembly modules together.
In your 7.2.4 ManualLinking\frontend\
folder, create a main.js
file and then open it with your editor.
The find_primes WebAssembly module expects a function that it can call to pass the prime number to the JavaScript code. You’ll create a logPrime function to pass to the module during instantiation that logs the value received from the module to the console window of the browser’s developer tools.
Add the following snippet to the main.js file:
function logPrime(prime) {
console.log(prime.toString());
}
Because the find_primes WebAssembly module is dependent on the IsPrime function in the is_prime module, you’ll need to download and instantiate the is_prime module first.
In the then
method of the instantiateStreaming call for the is_prime module:
- You’ll create an importObject for the find_primes WebAssembly module. The importObject is given the exported
_IsPrime
function from the is_prime module as well as the JavaScriptlogPrime
function. - You’ll then call the instantiateStreaming function for the find_primes WebAssembly module and return the Promise
The next then
method is for the successful download and instantiation of the find_primes WebAssembly module. In this block, you’ll call the _FindPrimes
function passing in a range of values to have the prime numbers within that range logged to the browser’s console window.
Add the code in listing 6 to the main.js file after the logPrime function.
Listing 6 The download and linking of two WebAssembly modules
…
const isPrimeImportObject = { #A
env: {
__memory_base: 0,
}
};
WebAssembly.instantiateStreaming(fetch("is_prime.wasm"), #B
isPrimeImportObject)
.then(module => { #C
const findPrimesImportObject = { #D
env: {
__memory_base: 0,
_IsPrime: module.instance.exports._IsPrime, #E
_LogPrime: logPrime, #F
}
};
return WebAssembly.instantiateStreaming(fetch("find_primes.wasm"), #G
findPrimesImportObject);
})
.then(module => { #H
module.instance.exports._FindPrimes(3, 100); #I
});
#A The importObject for the is_prime module
#B Download and instantiate the is_prime module
#C The is_prime is ready
#D The importObject for the find_primes module
#E The exported function is passed to the find_primes module
#F The JavaScript function is passed to the find_primes module
#G Download and instantiate the find_primes module. Return the instantiated module
#H The find_primes module is ready
#I Have the prime numbers between 3 and 100 displayed to the console window
Viewing the results
Now that you’ve created the HTML and JavaScript code, you can open a web browser and type http://localhost:8080/main.html
into the address box to see the webpage.
Press F12
to view the console window of the browser’s developer tools. You should see the prime numbers between 3 and 100 displayed similar to figure 20.
Now that you’ve learned how to implement dynamic linking using all three approaches, it’s time to compare them.
Dynamic linking review
The three approaches to dynamic linking you learned about in this article were:
- dlopen
- The side module needs to be downloaded to Emscripten’s file system first
- Calling dlopen returns you a handle to the side module file
- Passing the handle and the function name that you wish to call to the dlsym function returns you a function pointer to the function in the side module
- At this point, calling the function pointer is the same as calling a normal function in your module
- Because you’re requesting a function name based on the side module’s handle, a function with the same name in the main module won’t cause any problems.
- Linking to a side module is performed as needed
- dynamicLibraries
- Emscripten needs to be given a list of side modules that you want it to link to by including them in the dynamicLibraries array property of the Module object. This list needs to be specified before Emscripten’s JavaScript code is initialized.
- Emscripten handles downloading and linking the side module to the main module for you
- Your module’s code calls the functions in the side module the same way calls its own functions
- It’s impossible to call a function in another module if you already have a function with that name in the current module.
- All of the side modules specified are linked as soon as Emscripten’s JavaScript code is initialized
- The WebAssembly JavaScript API
- You handle downloading the WebAssembly module using the fetch method and then use the WebAssembly JavaScript API to instantiate that module.
- You then download the next WebAssembly module and pass the necessary exports from the first module as the imports for the current module.
- Your module’s code calls the functions in the side module the same way it calls its own functions
- Like with the dynamicLibraries approach, it’s impossible to call a function in another module if you already have a function with that name in the current module.
In summary, which approach to dynamic linking you want to use depends on how much control you want over the process and if you want that control in the module or in JavaScript.
- dlopen gives the dynamic linking control to the backend code. This is also the only approach which is possible if you need to call a function in a side module when you already have a function with that name in your main module.
- dynamicLibraries gives the dynamic linking control to the tooling where Emscripten does the work for you
- The WebAssembly JavaScript API gives the dynamic linking control to the frontend code where your JavaScript handles the linking.
That’s all for now. If you want to learn more about the book, check it out on liveBook here and see this slide deck.