Skip to content

Commit e78076e

Browse files
authored
Move some FS methods to FS_foo notation, allowing WasmFS to use them from JS APIs (#20098)
FS.createDataFile, FS.mkdirTree, FS.unlink are all now usable as FS_foo, that is, without the FS object being included. For the JS FS this is just a slightly odd refactoring, but for WasmFS it means we can use dependencies on a per-function basis, both for including them, and for including what they need. Also in WasmFS do the same for FS.create, FS.mknod, FS.mkdir, FS.writeFile as those are dependencies of the above. This may have a slight code size disadvantage in builds that include the FS object anyhow, but closure should be able to remove that anyhow. For the JS FS, FS is there all the time so we don't see any benefits really, but for WasmFS this allows us to use filesystem operations from JS APIs without bringing in all of FS (and without all the wasm code it calls into). WasmFS builds that do include FS - which is builds that have FORCE_FILESYSTEM enabled - may increase in size slightly, but those builds are rarer and large anyhow due to FS. With this, emscripten_async_wget can be converted to work in WasmFS. To do so we switch from FS.foo to FS_foo notation and add the proper deps, and then everything just works. Implements the ideas discussed in the later part of #20022
1 parent 0c35c4a commit e78076e

10 files changed

+158
-78
lines changed

src/library_fs.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,4 +1869,20 @@ FS.staticInit();` +
18691869
},
18701870
#endif
18711871
},
1872+
1873+
$FS_createDataFile__deps: ['$FS'],
1874+
$FS_createDataFile: (parent, name, fileData, canRead, canWrite, canOwn) => {
1875+
return FS.createDataFile(parent, name, fileData, canRead, canWrite, canOwn);
1876+
},
1877+
1878+
$FS_unlink__deps: ['$FS'],
1879+
$FS_unlink: (path) => FS.unlink(path),
1880+
1881+
$FS_mkdirTree__docs: `
1882+
/**
1883+
* @param {number=} mode Optionally, the mode to create in. Uses mkdir's
1884+
* default if not set.
1885+
*/`,
1886+
$FS_mkdirTree__deps: ['$FS'],
1887+
$FS_mkdirTree: (path, mode) => FS.mkdirTree(path, mode),
18721888
});

src/library_fs_shared.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ addToLibrary({
4545
$FS_createPreloadedFile__deps: [
4646
'$asyncLoad',
4747
'$PATH_FS',
48+
'$FS_createDataFile',
4849
#if !MINIMAL_RUNTIME
4950
'$FS_handledByPreloadPlugin',
5051
#endif
@@ -58,7 +59,7 @@ addToLibrary({
5859
function finish(byteArray) {
5960
if (preFinish) preFinish();
6061
if (!dontCreateFile) {
61-
FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn);
62+
FS_createDataFile(parent, name, byteArray, canRead, canWrite, canOwn);
6263
}
6364
if (onload) onload();
6465
removeRunDependency(dep);

src/library_wasmfs.js

Lines changed: 122 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ addToLibrary({
2323

2424
$FS__postset: `
2525
FS.init();
26-
FS.createPreloadedFile = FS_createPreloadedFile;
2726
`,
2827
$FS__deps: [
2928
'$MEMFS',
@@ -35,6 +34,7 @@ FS.createPreloadedFile = FS_createPreloadedFile;
3534
'$withStackSave',
3635
'$readI53FromI64',
3736
'$readI53FromU64',
37+
'$FS_createDataFile',
3838
'$FS_createPreloadedFile',
3939
'$FS_getMode',
4040
// For FS.readFile
@@ -44,6 +44,12 @@ FS.createPreloadedFile = FS_createPreloadedFile;
4444
// up requiring all of our code
4545
// here.
4646
'$FS_modeStringToFlags',
47+
'$FS_create',
48+
'$FS_mknod',
49+
'$FS_mkdir',
50+
'$FS_mkdirTree',
51+
'$FS_writeFile',
52+
'$FS_unlink',
4753
#if LibraryManager.has('library_icasefs.js')
4854
'$ICASEFS',
4955
#endif
@@ -93,19 +99,7 @@ FS.createPreloadedFile = FS_createPreloadedFile;
9399
FS.ErrnoError.prototype.constructor = FS.ErrnoError;
94100
},
95101
createDataFile(parent, name, fileData, canRead, canWrite, canOwn) {
96-
var pathName = name ? parent + '/' + name : parent;
97-
var mode = FS_getMode(canRead, canWrite);
98-
99-
if (!wasmFSPreloadingFlushed) {
100-
// WasmFS code in the wasm is not ready to be called yet. Cache the
101-
// files we want to create here in JS, and WasmFS will read them
102-
// later.
103-
wasmFSPreloadedFiles.push({pathName, fileData, mode});
104-
} else {
105-
// WasmFS is already running, so create the file normally.
106-
FS.create(pathName, mode);
107-
FS.writeFile(pathName, fileData);
108-
}
102+
return FS_createDataFile(parent, name, fileData, canRead, canWrite, canOwn);
109103
},
110104
createPath(parent, path, canRead, canWrite) {
111105
// Cache file path directory names.
@@ -124,6 +118,10 @@ FS.createPreloadedFile = FS_createPreloadedFile;
124118
return current;
125119
},
126120

121+
createPreloadedFile(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) {
122+
return FS_createPreloadedFile(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish);
123+
},
124+
127125
#if hasExportedSymbol('_wasmfs_read_file') // Support the JS function exactly
128126
// when the __wasmfs_* function is
129127
// present to be called (otherwise,
@@ -173,24 +171,8 @@ FS.createPreloadedFile = FS_createPreloadedFile;
173171

174172
// libc methods
175173

176-
mkdir: (path, mode) => FS.handleError(withStackSave(() => {
177-
mode = mode !== undefined ? mode : 511 /* 0777 */;
178-
var buffer = stringToUTF8OnStack(path);
179-
return __wasmfs_mkdir(buffer, mode);
180-
})),
181-
mkdirTree(path, mode) {
182-
var dirs = path.split('/');
183-
var d = '';
184-
for (var i = 0; i < dirs.length; ++i) {
185-
if (!dirs[i]) continue;
186-
d += '/' + dirs[i];
187-
try {
188-
FS.mkdir(d, mode);
189-
} catch(e) {
190-
if (e.errno != {{{ cDefs.EEXIST }}}) throw e;
191-
}
192-
}
193-
},
174+
mkdir: (path, mode) => FS_mkdir(path, mode),
175+
mkdirTree: (path, mode) => FS_mkdirTree(path, mode),
194176
rmdir: (path) => FS.handleError(
195177
withStackSave(() => __wasmfs_rmdir(stringToUTF8OnStack(path)))
196178
),
@@ -201,18 +183,9 @@ FS.createPreloadedFile = FS_createPreloadedFile;
201183
var fd = FS.handleError(__wasmfs_open(buffer, flags, mode));
202184
return { fd : fd };
203185
}),
204-
create(path, mode) {
205-
// Default settings copied from the legacy JS FS API.
206-
mode = mode !== undefined ? mode : 438 /* 0666 */;
207-
mode &= {{{ cDefs.S_IALLUGO }}};
208-
mode |= {{{ cDefs.S_IFREG }}};
209-
return FS.mknod(path, mode, 0);
210-
},
186+
create: (path, mode) => FS_create(path, mode),
211187
close: (stream) => FS.handleError(-__wasmfs_close(stream.fd)),
212-
unlink: (path) => withStackSave(() => {
213-
var buffer = stringToUTF8OnStack(path);
214-
return __wasmfs_unlink(buffer);
215-
}),
188+
unlink: (path) => FS_unlink(path),
216189
chdir: (path) => withStackSave(() => {
217190
var buffer = stringToUTF8OnStack(path);
218191
return __wasmfs_chdir(buffer);
@@ -260,6 +233,7 @@ FS.createPreloadedFile = FS_createPreloadedFile;
260233
allocate(stream, offset, length) {
261234
return FS.handleError(__wasmfs_allocate(stream.fd, {{{ splitI64('offset') }}}, {{{ splitI64('length') }}}));
262235
},
236+
writeFile: (path, data) => FS_writeFile(path, data),
263237
mmap: (stream, length, offset, prot, flags) => {
264238
var buf = FS.handleError(__wasmfs_mmap(length, prot, flags, stream.fd, {{{ splitI64('offset') }}}));
265239
return { ptr: buf, allocated: true };
@@ -273,24 +247,6 @@ FS.createPreloadedFile = FS_createPreloadedFile;
273247
munmap: (addr, length) => (
274248
FS.handleError(__wasmfs_munmap(addr, length))
275249
),
276-
writeFile: (path, data) => withStackSave(() => {
277-
var pathBuffer = stringToUTF8OnStack(path);
278-
if (typeof data == 'string') {
279-
var buf = new Uint8Array(lengthBytesUTF8(data) + 1);
280-
var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length);
281-
data = buf.slice(0, actualNumBytes);
282-
}
283-
var dataBuffer = _malloc(data.length);
284-
#if ASSERTIONS
285-
assert(dataBuffer);
286-
#endif
287-
for (var i = 0; i < data.length; i++) {
288-
{{{ makeSetValue('dataBuffer', 'i', 'data[i]', 'i8') }}};
289-
}
290-
var ret = __wasmfs_write_file(pathBuffer, dataBuffer, data.length);
291-
_free(dataBuffer);
292-
return ret;
293-
}),
294250
symlink: (target, linkpath) => withStackSave(() => (
295251
__wasmfs_symlink(stringToUTF8OnStack(target), stringToUTF8OnStack(linkpath))
296252
)),
@@ -402,12 +358,7 @@ FS.createPreloadedFile = FS_createPreloadedFile;
402358
FS.handleError(withStackSave(() => __wasmfs_unmount(stringToUTF8OnStack(mountpoint))))
403359
),
404360
// TODO: lookup
405-
mknod(path, mode, dev) {
406-
return FS.handleError(withStackSave(() => {
407-
var pathBuffer = stringToUTF8OnStack(path);
408-
return __wasmfs_mknod(pathBuffer, mode, dev);
409-
}));
410-
},
361+
mknod: (path, mode, dev) => FS_mknod(path, mode, dev),
411362
makedev: (ma, mi) => ((ma) << 8 | (mi)),
412363
registerDevice(dev, ops) {
413364
var backendPointer = _wasmfs_create_jsimpl_backend();
@@ -522,6 +473,110 @@ FS.createPreloadedFile = FS_createPreloadedFile;
522473
#endif
523474
},
524475

476+
// Split-out FS.* methods. These are split out for code size reasons, so that
477+
// we can include the ones we need on demand, rather than put them all on the
478+
// main FS object. As a result the entire FS object is not needed if you just
479+
// need some specific FS_* operations. When the FS object is present, it calls
480+
// into those FS_* methods as needed.
481+
//
482+
// In contrast, the old JS FS (library_fs.js) does the opposite: it puts all
483+
// things on the FS object, and copies them to FS_* methods for use from JS
484+
// library code. Given that the JS FS is implemented entirely in JS, that
485+
// makes sense there (as almost all that FS object ends up needed anyhow all
486+
// the time).
487+
488+
$FS_createDataFile__deps: [
489+
'$wasmFSPreloadingFlushed', '$wasmFSPreloadedFiles',
490+
'$FS_create', '$FS_writeFile',
491+
],
492+
$FS_createDataFile: (parent, name, fileData, canRead, canWrite, canOwn) => {
493+
var pathName = name ? parent + '/' + name : parent;
494+
var mode = FS_getMode(canRead, canWrite);
495+
496+
if (!wasmFSPreloadingFlushed) {
497+
// WasmFS code in the wasm is not ready to be called yet. Cache the
498+
// files we want to create here in JS, and WasmFS will read them
499+
// later.
500+
wasmFSPreloadedFiles.push({pathName, fileData, mode});
501+
} else {
502+
// WasmFS is already running, so create the file normally.
503+
FS_create(pathName, mode);
504+
FS_writeFile(pathName, fileData);
505+
}
506+
},
507+
508+
$FS_mknod__deps: ['_wasmfs_mknod'],
509+
$FS_mknod: (path, mode, dev) => {
510+
return FS.handleError(withStackSave(() => {
511+
var pathBuffer = stringToUTF8OnStack(path);
512+
return __wasmfs_mknod(pathBuffer, mode, dev);
513+
}));
514+
},
515+
516+
$FS_create__deps: ['$FS_mknod'],
517+
$FS_create: (path, mode) => {
518+
// Default settings copied from the legacy JS FS API.
519+
mode = mode !== undefined ? mode : 438 /* 0666 */;
520+
mode &= {{{ cDefs.S_IALLUGO }}};
521+
mode |= {{{ cDefs.S_IFREG }}};
522+
return FS_mknod(path, mode, 0);
523+
},
524+
525+
$FS_writeFile__deps: ['_wasmfs_write_file'],
526+
$FS_writeFile: (path, data) => withStackSave(() => {
527+
var pathBuffer = stringToUTF8OnStack(path);
528+
if (typeof data == 'string') {
529+
var buf = new Uint8Array(lengthBytesUTF8(data) + 1);
530+
var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length);
531+
data = buf.slice(0, actualNumBytes);
532+
}
533+
var dataBuffer = _malloc(data.length);
534+
#if ASSERTIONS
535+
assert(dataBuffer);
536+
#endif
537+
for (var i = 0; i < data.length; i++) {
538+
{{{ makeSetValue('dataBuffer', 'i', 'data[i]', 'i8') }}};
539+
}
540+
var ret = __wasmfs_write_file(pathBuffer, dataBuffer, data.length);
541+
_free(dataBuffer);
542+
return ret;
543+
}),
544+
545+
$FS_mkdir__deps: ['_wasmfs_mkdir'],
546+
$FS_mkdir: (path, mode) => FS.handleError(withStackSave(() => {
547+
mode = mode !== undefined ? mode : 511 /* 0777 */;
548+
var buffer = stringToUTF8OnStack(path);
549+
return __wasmfs_mkdir(buffer, mode);
550+
})),
551+
552+
$FS_mkdirTree__docs: `
553+
/**
554+
* @param {number=} mode Optionally, the mode to create in. Uses mkdir's
555+
* default if not set.
556+
*/`,
557+
$FS_mkdirTree__deps: ['$FS_mkdir'],
558+
$FS_mkdirTree: (path, mode) => {
559+
var dirs = path.split('/');
560+
var d = '';
561+
for (var i = 0; i < dirs.length; ++i) {
562+
if (!dirs[i]) continue;
563+
d += '/' + dirs[i];
564+
try {
565+
FS_mkdir(d, mode);
566+
} catch(e) {
567+
if (e.errno != {{{ cDefs.EEXIST }}}) throw e;
568+
}
569+
}
570+
},
571+
572+
$FS_unlink__deps: ['_wasmfs_unlink'],
573+
$FS_unlink: (path) => withStackSave(() => {
574+
var buffer = stringToUTF8OnStack(path);
575+
return __wasmfs_unlink(buffer);
576+
}),
577+
578+
// Wasm access calls.
579+
525580
_wasmfs_get_num_preloaded_files__deps: [
526581
'$wasmFSPreloadedFiles',
527582
'$wasmFSPreloadingFlushed'],

src/library_wget.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ var LibraryWget = {
1616
},
1717
},
1818

19-
emscripten_async_wget__deps: ['$PATH_FS', '$wget', '$callUserCallback', '$Browser', '$withStackSave', '$stringToUTF8OnStack'],
19+
emscripten_async_wget__deps: [
20+
'$PATH_FS', '$wget', '$callUserCallback', '$Browser',
21+
'$withStackSave', '$stringToUTF8OnStack',
22+
'$FS_mkdirTree',
23+
'$FS_createPreloadedFile',
24+
'$FS_unlink',
25+
],
2026
emscripten_async_wget__proxy: 'sync',
2127
emscripten_async_wget: (url, file, onload, onerror) => {
2228
{{{ runtimeKeepalivePush() }}}
@@ -35,7 +41,7 @@ var LibraryWget = {
3541
}
3642
}
3743
var destinationDirectory = PATH.dirname(_file);
38-
FS.createPreloadedFile(
44+
FS_createPreloadedFile(
3945
destinationDirectory,
4046
PATH.basename(_file),
4147
_url, true, true,
@@ -50,10 +56,10 @@ var LibraryWget = {
5056
function() { // preFinish
5157
// if a file exists there, we overwrite it
5258
try {
53-
FS.unlink(_file);
59+
FS_unlink(_file);
5460
} catch (e) {}
5561
// if the destination directory does not yet exist, create it
56-
FS.mkdirTree(destinationDirectory);
62+
FS_mkdirTree(destinationDirectory);
5763
}
5864
);
5965
},

src/modules.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,12 +371,10 @@ function exportRuntime() {
371371
'removeRunDependency',
372372
'FS_createFolder',
373373
'FS_createPath',
374-
'FS_createDataFile',
375374
'FS_createLazyFile',
376375
'FS_createLink',
377376
'FS_createDevice',
378377
'FS_readFile',
379-
'FS_unlink',
380378
'out',
381379
'err',
382380
'callMain',
@@ -455,6 +453,9 @@ function exportRuntime() {
455453
for (const ident of Object.keys(LibraryManager.library)) {
456454
if (isJsOnlySymbol(ident) && !isDecorator(ident) && !isInternalSymbol(ident)) {
457455
const jsname = ident.substr(1);
456+
// Note that this assertion may be hit when a function is moved into the
457+
// JS library. In that case the function should be removed from the list
458+
// of runtime elements above.
458459
assert(!runtimeElementsSet.has(jsname), 'runtimeElements contains library symbol: ' + ident);
459460
runtimeElements.push(jsname);
460461
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
23669
1+
23682
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20140
1+
20153
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
58694
1+
58712
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
57633
1+
57651

test/test_browser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,6 +1915,7 @@ def setup():
19151915
def test_emscripten_api_infloop(self):
19161916
self.btest_exit('emscripten_api_browser_infloop.cpp', assert_returncode=7)
19171917

1918+
@also_with_wasmfs
19181919
def test_emscripten_fs_api(self):
19191920
shutil.copyfile(test_file('screenshot.png'), 'screenshot.png') # preloaded *after* run
19201921
self.btest_exit('emscripten_fs_api_browser.c', assert_returncode=1, args=['-lSDL'])

0 commit comments

Comments
 (0)