Skip to content

Commit ab0703f

Browse files
committed
Move dlopen file operations into native code. NFC
This allows the file data to be read by just a single thread (the calling thread) and shared with all the others via shared memory. Fixes: #19245
1 parent ab6e3ee commit ab0703f

File tree

9 files changed

+94
-56
lines changed

9 files changed

+94
-56
lines changed

emcc.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2307,12 +2307,6 @@ def phase_linker_setup(options, state, newargs):
23072307
settings.SYSCALLS_REQUIRE_FILESYSTEM = 0
23082308
settings.JS_LIBRARIES.append((0, 'library_wasmfs.js'))
23092309
settings.REQUIRED_EXPORTS += ['_wasmfs_read_file']
2310-
if settings.MAIN_MODULE:
2311-
# Dynamic library support uses JS API internals, so include it all
2312-
# TODO: rewriting more of the dynamic linking support code into wasm could
2313-
# avoid this. also, after we remove the old FS, we could write a
2314-
# more specific API for wasmfs/dynamic linking integration perhaps
2315-
settings.FORCE_FILESYSTEM = 1
23162310
if settings.FORCE_FILESYSTEM:
23172311
# Add exports for the JS API. Like the old JS FS, WasmFS by default
23182312
# includes just what JS parts it actually needs, and FORCE_FILESYSTEM is

src/generated_struct_info32.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,12 +1321,14 @@
13211321
"d_type": 18
13221322
},
13231323
"dso": {
1324-
"__size__": 28,
1324+
"__size__": 36,
1325+
"file_data": 28,
1326+
"file_data_size": 32,
13251327
"flags": 4,
13261328
"mem_addr": 12,
13271329
"mem_allocated": 8,
13281330
"mem_size": 16,
1329-
"name": 28,
1331+
"name": 36,
13301332
"table_addr": 20,
13311333
"table_size": 24
13321334
},

src/generated_struct_info64.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,12 +1321,14 @@
13211321
"d_type": 18
13221322
},
13231323
"dso": {
1324-
"__size__": 48,
1324+
"__size__": 64,
1325+
"file_data": 48,
1326+
"file_data_size": 56,
13251327
"flags": 8,
13261328
"mem_addr": 16,
13271329
"mem_allocated": 12,
13281330
"mem_size": 24,
1329-
"name": 48,
1331+
"name": 64,
13301332
"table_addr": 32,
13311333
"table_size": 40
13321334
},

src/library_dylink.js

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -905,10 +905,6 @@ var LibraryDylink = {
905905
// - if flags.loadAsync=true, the loading is performed asynchronously and
906906
// loadDynamicLibrary returns corresponding promise.
907907
//
908-
// - if flags.fs is provided, it is used as FS-like interface to load library data.
909-
// By default, when flags.fs=undefined, native loading capabilities of the
910-
// environment are used.
911-
//
912908
// If a library was already loaded, it is not loaded a second time. However
913909
// flags.global and flags.nodelete are handled every time a load request is made.
914910
// Once a library becomes "global" or "nodelete", it cannot be removed or unloaded.
@@ -960,12 +956,13 @@ var LibraryDylink = {
960956
// libName -> libData
961957
function loadLibData() {
962958
// for wasm, we can use fetch for async, but for fs mode we can only imitate it
963-
if (flags.fs && flags.fs.findObject(libName)) {
964-
var libData = flags.fs.readFile(libName, {encoding: 'binary'});
965-
if (!(libData instanceof Uint8Array)) {
966-
libData = new Uint8Array(libData);
959+
if (handle) {
960+
var data = {{{ makeGetValue('handle', C_STRUCTS.dso.file_data, '*') }}};
961+
var dataSize = {{{ makeGetValue('handle', C_STRUCTS.dso.file_data_size, '*') }}};
962+
if (data && dataSize) {
963+
var libData = HEAP8.slice(data, data + dataSize);
964+
return flags.loadAsync ? Promise.resolve(libData) : libData;
967965
}
968-
return flags.loadAsync ? Promise.resolve(libData) : libData;
969966
}
970967

971968
var libFile = locateFile(libName);
@@ -987,7 +984,7 @@ var LibraryDylink = {
987984
// lookup preloaded cache first
988985
if (preloadedWasm[libName]) {
989986
#if DYLINK_DEBUG
990-
err('using preloaded module for: ' + libName);
987+
dbg('using preloaded module for: ' + libName);
991988
#endif
992989
var libModule = preloadedWasm[libName];
993990
return flags.loadAsync ? Promise.resolve(libModule) : libModule;
@@ -1059,11 +1056,12 @@ var LibraryDylink = {
10591056
},
10601057

10611058
// void* dlopen(const char* filename, int flags);
1062-
$dlopenInternal__deps: ['$FS', '$ENV', '$dlSetError', '$PATH'],
1059+
$dlopenInternal__deps: ['$ENV', '$dlSetError', '$PATH'],
10631060
$dlopenInternal: function(handle, jsflags) {
10641061
// void *dlopen(const char *file, int mode);
10651062
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html
1066-
var filename = UTF8ToString(handle + {{{ C_STRUCTS.dso.name }}});
1063+
var filenamePtr = handle + {{{ C_STRUCTS.dso.name }}};
1064+
var filename = UTF8ToString(filenamePtr);
10671065
var flags = {{{ makeGetValue('handle', C_STRUCTS.dso.flags, 'i32') }}};
10681066
#if DYLINK_DEBUG
10691067
dbg('dlopenInternal: ' + filename);
@@ -1079,7 +1077,6 @@ var LibraryDylink = {
10791077
global,
10801078
nodelete: Boolean(flags & {{{ cDefs.RTLD_NODELETE }}}),
10811079
loadAsync: jsflags.loadAsync,
1082-
fs: jsflags.fs,
10831080
}
10841081

10851082
if (jsflags.loadAsync) {
@@ -1104,18 +1101,12 @@ var LibraryDylink = {
11041101
_dlopen_js: function(handle) {
11051102
#if ASYNCIFY
11061103
return Asyncify.handleSleep((wakeUp) => {
1107-
var jsflags = {
1108-
loadAsync: true,
1109-
fs: FS, // load libraries from provided filesystem
1110-
}
1104+
var jsflags = { loadAsync: true }
11111105
var promise = dlopenInternal(handle, jsflags);
11121106
promise.then(wakeUp).catch(() => wakeUp(0));
11131107
});
11141108
#else
1115-
var jsflags = {
1116-
loadAsync: false,
1117-
fs: FS, // load libraries from provided filesystem
1118-
}
1109+
var jsflags = { loadAsync: false }
11191110
return dlopenInternal(handle, jsflags);
11201111
#endif
11211112
},
@@ -1125,8 +1116,8 @@ var LibraryDylink = {
11251116
_emscripten_dlopen_js: function(handle, onsuccess, onerror, user_data) {
11261117
/** @param {Object=} e */
11271118
function errorCallback(e) {
1128-
var filename = UTF8ToString({{{ makeGetValue('handle', C_STRUCTS.dso.name, '*') }}});
1129-
dlSetError('Could not load dynamic lib: ' + filename + '\n' + e);
1119+
var filename = UTF8ToString(handle + {{{ C_STRUCTS.dso.name }}});
1120+
dlSetError('Could not load dynamic libX: ' + filename + '\n' + e);
11301121
{{{ runtimeKeepalivePop() }}}
11311122
callUserCallback(() => {{{ makeDynCall('vpp', 'onerror') }}}(handle, user_data));
11321123
}
@@ -1136,7 +1127,8 @@ var LibraryDylink = {
11361127
}
11371128

11381129
{{{ runtimeKeepalivePush() }}}
1139-
var promise = dlopenInternal(handle, { loadAsync: true });
1130+
var jsflags = { loadAsync: true }
1131+
var promise = dlopenInternal(handle, jsflags);
11401132
if (promise) {
11411133
promise.then(successCallback, errorCallback);
11421134
} else {

src/struct_info_internal.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
"mem_size",
4141
"table_addr",
4242
"table_size",
43+
"file_data",
44+
"file_data_size",
4345
"name"
4446
]
4547
}

system/lib/libc/dynlink.c

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#define _GNU_SOURCE
1212
#include <assert.h>
1313
#include <dlfcn.h>
14+
#include <fcntl.h>
1415
#include <pthread.h>
1516
#include <threads.h>
1617
#include <stdarg.h>
@@ -19,6 +20,7 @@
1920
#include <stdlib.h>
2021
#include <string.h>
2122
#include <sys/stat.h>
23+
#include <unistd.h>
2224

2325
#include <emscripten/console.h>
2426
#include <emscripten/threading.h>
@@ -81,6 +83,8 @@ static thread_local struct dlevent* thread_local_tail = &main_event;
8183
static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER;
8284
static thread_local bool skip_dlsync = false;
8385

86+
static void dlsync();
87+
8488
static void do_write_lock() {
8589
// Once we have the lock we want to avoid automatic code sync as that would
8690
// result in a deadlock.
@@ -155,6 +159,14 @@ static void load_library_done(struct dso* p) {
155159
p->table_addr,
156160
p->table_size);
157161
new_dlevent(p, -1);
162+
#ifdef _REENTRANT
163+
// Block until all other threads have loaded this module.
164+
dlsync();
165+
#endif
166+
if (p->file_data) {
167+
free(p->file_data);
168+
p->file_data_size = 0;
169+
}
158170
}
159171

160172
static struct dso* load_library_start(const char* name, int flags) {
@@ -169,6 +181,27 @@ static struct dso* load_library_start(const char* name, int flags) {
169181
p->flags = flags;
170182
strcpy(p->name, name);
171183

184+
// If the file exists in the filesystem, load it here into linear memory which
185+
// makes the data available to JS, and to other threads. This data gets
186+
// free'd later once all threads have loaded the DSO.
187+
struct stat statbuf;
188+
if (stat(name, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
189+
int fd = open(name, O_RDONLY);
190+
if (fd >= 0) {
191+
off_t size = lseek(fd, 0, SEEK_END);
192+
if (size != (off_t)-1) {
193+
lseek(fd, 0, SEEK_SET);
194+
p->file_data = malloc(size);
195+
if (read(fd, p->file_data, size) == size) {
196+
p->file_data_size = size;
197+
} else {
198+
free(p->file_data);
199+
}
200+
}
201+
close(fd);
202+
}
203+
}
204+
172205
return p;
173206
}
174207

@@ -424,10 +457,6 @@ static void dlopen_onsuccess(struct dso* dso, void* user_data) {
424457
dso->mem_addr,
425458
dso->mem_size);
426459
load_library_done(dso);
427-
#ifdef _REENTRANT
428-
// Block until all other threads have loaded this module.
429-
dlsync();
430-
#endif
431460
do_write_unlock();
432461
data->onsuccess(data->user_data, dso);
433462
free(data);
@@ -526,10 +555,6 @@ static struct dso* _dlopen(const char* file, int flags) {
526555
}
527556
dbg("dlopen_js: success: %p", p);
528557
load_library_done(p);
529-
#ifdef _REENTRANT
530-
// Block until all other threads have loaded this module.
531-
dlsync();
532-
#endif
533558
end:
534559
dbg("dlopen(%s): done: %p", file, p);
535560
do_write_unlock();

system/lib/libc/musl/src/internal/dynlink.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ struct dso {
2929
void* table_addr;
3030
size_t table_size;
3131

32+
// For DSO load events, where the DSO comes from a file on disc, this
33+
// is a pointer the file data read in by the laoding thread and shared with
34+
// others.
35+
uint8_t* file_data;
36+
size_t file_data_size;
37+
3238
// Flexible array; must be final element of struct
3339
char name[];
3440
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
28151
1+
28007

test/test_other.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6371,7 +6371,13 @@ def test_RUNTIME_LINKED_LIBS(self):
63716371

63726372
self.assertBinaryEqual('main.wasm', 'main2.wasm')
63736373

6374-
def test_ld_library_path(self):
6374+
@parameterized({
6375+
'': ([],),
6376+
'pthread': (['-g', '-pthread', '-Wno-experimental', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
6377+
})
6378+
def test_ld_library_path(self, args):
6379+
if args:
6380+
self.setup_node_pthreads()
63756381
create_file('hello1.c', r'''
63766382
#include <stdio.h>
63776383

@@ -6456,17 +6462,17 @@ def test_ld_library_path(self):
64566462
return 0;
64576463
}
64586464
''')
6459-
self.run_process([EMCC, '-o', 'hello1.wasm', 'hello1.c', '-sSIDE_MODULE'])
6460-
self.run_process([EMCC, '-o', 'hello2.wasm', 'hello2.c', '-sSIDE_MODULE'])
6461-
self.run_process([EMCC, '-o', 'hello3.wasm', 'hello3.c', '-sSIDE_MODULE'])
6462-
self.run_process([EMCC, '-o', 'hello4.wasm', 'hello4.c', '-sSIDE_MODULE'])
6465+
self.run_process([EMCC, '-o', 'hello1.wasm', 'hello1.c', '-sSIDE_MODULE'] + args)
6466+
self.run_process([EMCC, '-o', 'hello2.wasm', 'hello2.c', '-sSIDE_MODULE'] + args)
6467+
self.run_process([EMCC, '-o', 'hello3.wasm', 'hello3.c', '-sSIDE_MODULE'] + args)
6468+
self.run_process([EMCC, '-o', 'hello4.wasm', 'hello4.c', '-sSIDE_MODULE'] + args)
64636469
self.run_process([EMCC, '--profiling-funcs', '-o', 'main.js', 'main.c', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
64646470
'--embed-file', 'hello1.wasm@/lib/libhello1.wasm',
64656471
'--embed-file', 'hello2.wasm@/usr/lib/libhello2.wasm',
64666472
'--embed-file', 'hello3.wasm@/libhello3.wasm',
64676473
'--embed-file', 'hello4.wasm@/usr/local/lib/libhello4.wasm',
64686474
'hello1.wasm', 'hello2.wasm', 'hello3.wasm', 'hello4.wasm', '-sNO_AUTOLOAD_DYLIBS',
6469-
'--pre-js', 'pre.js'])
6475+
'--pre-js', 'pre.js'] + args)
64706476
out = self.run_js('main.js')
64716477
self.assertContained('Hello1', out)
64726478
self.assertContained('Hello2', out)
@@ -13399,7 +13405,13 @@ def test_windows_batch_file_dp0_expansion_bug(self):
1339913405
create_file('build_with_quotes.bat', f'@"emcc" {test_file("hello_world.c")}')
1340013406
self.run_process(['build_with_quotes.bat'])
1340113407

13402-
def test_preload_module(self):
13408+
@parameterized({
13409+
'': ([],),
13410+
'pthread': (['-g', '-pthread', '-Wno-experimental', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
13411+
})
13412+
def test_preload_module(self, args):
13413+
if args:
13414+
self.setup_node_pthreads()
1340313415
# TODO(sbc): This test is copyied from test_browser.py. Perhaps find a better way to
1340413416
# share code between them.
1340513417
create_file('library.c', r'''
@@ -13408,17 +13420,20 @@ def test_preload_module(self):
1340813420
return 42;
1340913421
}
1341013422
''')
13411-
self.run_process([EMCC, 'library.c', '-sSIDE_MODULE', '-o', 'library.so'])
13423+
self.run_process([EMCC, 'library.c', '-sSIDE_MODULE', '-o', 'library.so'] + args)
1341213424
create_file('main.c', r'''
1341313425
#include <assert.h>
1341413426
#include <dlfcn.h>
1341513427
#include <stdio.h>
1341613428
#include <emscripten.h>
13429+
#include <emscripten/threading.h>
1341713430
int main() {
13418-
int found = EM_ASM_INT(
13419-
return preloadedWasm['/library.so'] !== undefined;
13420-
);
13421-
assert(found);
13431+
if (emscripten_is_main_runtime_thread()) {
13432+
int found = EM_ASM_INT(
13433+
return preloadedWasm['/library.so'] !== undefined;
13434+
);
13435+
assert(found);
13436+
}
1342213437
void *lib_handle = dlopen("/library.so", RTLD_NOW);
1342313438
assert(lib_handle);
1342413439
typedef int (*voidfunc)();
@@ -13429,4 +13444,4 @@ def test_preload_module(self):
1342913444
return 0;
1343013445
}
1343113446
''')
13432-
self.do_runf('main.c', 'done\n', emcc_args=['-sMAIN_MODULE=2', '--preload-file', '.@/', '--use-preload-plugins'])
13447+
self.do_runf('main.c', 'done\n', emcc_args=['-sMAIN_MODULE=2', '--preload-file', '.@/', '--use-preload-plugins'] + args)

0 commit comments

Comments
 (0)