Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ See docs/process.md for more on how version tagging works.
`{{{ cDefs.FOO }}}`) are now exposed as `const` JavaScript library symbols
(e.g., `ENOENT`, `O_CREAT`) and referenced directly in JS library code,
making unminified output more readable. (#28144)
- In pthreads builds with `MAIN_MODULE`, workers no longer walk the main
module's whole function table, which created a JS function object for every
table entry in every worker. The main thread now passes them the table slots
of the main module's exported functions. As a result, calling `addFunction`
in a worker on a main-module function that is not exported (for example one
obtained with `getWasmTableEntry`) now allocates a new table slot instead of
returning its existing address. (#28105)

7.0.0 - 11/06/26
----------------
Expand Down
29 changes: 29 additions & 0 deletions src/lib/libdylink.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,35 @@ var LibraryDylink = {
;
},

#if PTHREADS
// Called once the main module is instantiated, before updateGOT. The first
// getFunctionAddress walks the whole table, creating a JS function object for
// every entry, which every worker would keep for its lifetime. The dynamic
// linker only looks up the main module's exported functions, so the main
// thread records their table slots and workers start functionsInTableMap
// with just those. Side modules add their own slots when they are loaded.
// As a consequence, addFunction in a worker on a main-module function that
// is in the table but not exported gives it a new slot (ASSERTIONS=2 reports
// this).
$initFunctionsInTableMap__internal: true,
$initFunctionsInTableMap: (exports) => {
if (!ENVIRONMENT_IS_PTHREAD) {
mainModuleExportSlots = [];
for (var value of Object.values(exports)) {
var addr = typeof value == 'function' && getFunctionAddress(value);
if (addr) {
mainModuleExportSlots.push(addr);
}
}
} else if (mainModuleExportSlots) {
functionsInTableMap = new WeakMap();
for (var slot of mainModuleExportSlots) {
functionsInTableMap.set(getWasmTableEntry(slot), slot);
}
}
},
#endif

$updateGOT__internal: true,
$updateGOT__docs: '/** @param {boolean=} replace */',
$updateGOT: (exports, replace) => {
Expand Down
1 change: 1 addition & 0 deletions src/lib/libpthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ var LibraryPThread = {
// Share all modules that have been loaded so far. New workers
// won't start running threads until these are all loaded.
sharedModules,
mainModuleExportSlots,
#endif
#if ASSERTIONS
workerID: worker.workerID,
Expand Down
3 changes: 3 additions & 0 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,9 @@ function getWasmImports() {
assignWasmExports(wasmExports);

#if MAIN_MODULE
#if PTHREADS
initFunctionsInTableMap(origExports);
#endif
updateGOT(origExports);
#endif

Expand Down
6 changes: 6 additions & 0 deletions src/runtime_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
// Map of modules to be shared with new threads. This gets populated by the
// main thread and shared with all new workers via the initial `load` message.
var sharedModules = {};

// Table slots of the main module's exported functions. Recorded by the main
// thread and shared with all new workers via the initial `load` message (see
// initFunctionsInTableMap).
var mainModuleExportSlots;
#endif

var startWorker;
Expand Down Expand Up @@ -67,6 +72,7 @@ if (ENVIRONMENT_IS_PTHREAD) {
#if MAIN_MODULE
dynamicLibraries = msgData.dynamicLibraries;
sharedModules = msgData.sharedModules;
mainModuleExportSlots = msgData.mainModuleExportSlots;
#if RUNTIME_DEBUG
dbg(`worker: received ${Object.keys(msgData.sharedModules).length} shared modules: ${Object.keys(msgData.sharedModules)}`);
#endif
Expand Down
3 changes: 3 additions & 0 deletions test/core/pthread/test_pthread_dylink.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include "test_pthread_dylink.h"
Expand All @@ -13,6 +14,8 @@ void print_addresses() {
printf("main_module: &foo_side %p -> %d\n", &foo_side, foo_side);
printf("main_module: &bar_main %p\n", &bar_main);
printf("main_module: &bar_side %p\n", &bar_side);
// Each function has one address in every module and every thread.
assert(bar_main_addr_side() == &bar_main);
}

void* thread_main(void* arg) {
Expand Down
3 changes: 3 additions & 0 deletions test/core/pthread/test_pthread_dylink.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ int bar_main(); // Function defined in main
int bar_side(); // Function defined in side

void print_addresses_side();

typedef int (*bar_func)();
bar_func bar_main_addr_side(); // &bar_main, as seen by side
4 changes: 4 additions & 0 deletions test/core/pthread/test_pthread_dylink_side.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ void print_addresses_side() {
printf("side_module: &bar_main %p\n", &bar_main);
printf("side_module: &bar_side %p\n", &bar_side);
}

bar_func bar_main_addr_side() {
return &bar_main;
}
2 changes: 2 additions & 0 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,8 @@ def limit_incoming_module_api():
settings.INCLUDE_FULL_LIBRARY = 1
# Called from preamble.js once the main module is instantiated.
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$loadDylibs']
if settings.PTHREADS:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$initFunctionsInTableMap']
# `__stack_pointer` is exported for PIC side modules. `__heap_base` is used
# by `$getMemory` in `libdylink.js` (cannot be in `$getMemory__deps` because
# `wasm-ld` processes stub library dependencies before synthesizing optional
Expand Down
Loading