Skip to content

Commit ac85c42

Browse files
authored
WasmFS JS API: Implement mmap (#20019)
This PR implements mmap, msync, and munmap, and adds JS API tests to test_fs_js_api.
1 parent 9c0efe9 commit ac85c42

File tree

4 files changed

+92
-4
lines changed

4 files changed

+92
-4
lines changed

emcc.py

100755100644
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,7 @@ def phase_linker_setup(options, state, newargs):
23582358
# included, as the entire JS library can refer to things that require
23592359
# these exports.)
23602360
settings.REQUIRED_EXPORTS += [
2361+
'emscripten_builtin_memalign',
23612362
'wasmfs_create_file',
23622363
'_wasmfs_mount',
23632364
'_wasmfs_unmount',
@@ -2374,6 +2375,9 @@ def phase_linker_setup(options, state, newargs):
23742375
'_wasmfs_chdir',
23752376
'_wasmfs_mknod',
23762377
'_wasmfs_rmdir',
2378+
'_wasmfs_mmap',
2379+
'_wasmfs_munmap',
2380+
'_wasmfs_msync',
23772381
'_wasmfs_read',
23782382
'_wasmfs_pread',
23792383
'_wasmfs_symlink',

src/library_wasmfs.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,19 @@ FS.createPreloadedFile = FS_createPreloadedFile;
260260
allocate(stream, offset, length) {
261261
return FS.handleError(__wasmfs_allocate(stream.fd, {{{ splitI64('offset') }}}, {{{ splitI64('length') }}}));
262262
},
263-
// TODO: mmap
264-
// TODO: msync
265-
// TODO: munmap
263+
mmap: (stream, length, offset, prot, flags) => {
264+
var buf = FS.handleError(__wasmfs_mmap(length, prot, flags, stream.fd, {{{ splitI64('offset') }}}));
265+
return { ptr: buf, allocated: true };
266+
},
267+
// offset is passed to msync to maintain backwards compatability with the legacy JS API but is not used by WasmFS.
268+
msync: (stream, bufferPtr, offset, length, mmapFlags) => {
269+
assert(offset === 0);
270+
// TODO: assert that stream has the fd corresponding to the mapped buffer (bufferPtr).
271+
return FS.handleError(__wasmfs_msync(bufferPtr, length, mmapFlags));
272+
},
273+
munmap: (addr, length) => (
274+
FS.handleError(__wasmfs_munmap(addr, length))
275+
),
266276
writeFile: (path, data) => withStackSave(() => {
267277
var pathBuffer = stringToUTF8OnStack(path);
268278
if (typeof data == 'string') {

system/lib/wasmfs/js_api.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,18 @@ int _wasmfs_ftruncate(int fd, off_t length) {
253253

254254
int _wasmfs_close(int fd) { return __wasi_fd_close(fd); }
255255

256+
int _wasmfs_mmap(size_t length, int prot, int flags, int fd, off_t offset) {
257+
return __syscall_mmap2(0, length, prot, flags, fd, offset);
258+
}
259+
260+
int _wasmfs_msync(void* addr, size_t length, int flags) {
261+
return __syscall_msync((intptr_t)addr, length, flags);
262+
}
263+
264+
int _wasmfs_munmap(void* addr, size_t length) {
265+
return __syscall_munmap((intptr_t)addr, length);
266+
}
267+
256268
int _wasmfs_utime(char* path, long atime_ms, long mtime_ms) {
257269
struct timespec times[2];
258270
times[0].tv_sec = atime_ms / 1000;
@@ -261,7 +273,7 @@ int _wasmfs_utime(char* path, long atime_ms, long mtime_ms) {
261273
times[1].tv_nsec = (mtime_ms % 1000) * 1000000;
262274

263275
return __syscall_utimensat(AT_FDCWD, (intptr_t)path, (intptr_t)times, 0);
264-
};
276+
}
265277

266278
int _wasmfs_stat(char* path, struct stat* statBuf) {
267279
return __syscall_stat64((intptr_t)path, (intptr_t)statBuf);

test/fs/test_fs_js_api.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <emscripten/emscripten.h>
99
#include <stdio.h>
10+
#include <string.h>
1011
#include <unistd.h>
1112
#include <sys/stat.h>
1213
#include <assert.h>
@@ -333,6 +334,63 @@ void test_fs_truncate() {
333334
remove("truncatetest");
334335
}
335336

337+
void test_fs_mmap() {
338+
EM_ASM(
339+
FS.writeFile('mmaptest', 'a=1_b=2_');
340+
341+
var stream = FS.open('mmaptest', 'r+');
342+
assert(stream);
343+
344+
var mapped = FS.mmap(stream, 12, 0, 1 | 2 /* PROT_READ | PROT_WRITE */, 1 /* MAP_SHARED */);
345+
var ret = new Uint8Array(Module.HEAPU8.subarray(mapped.ptr, mapped.ptr + 12));
346+
var fileContents = "";
347+
for (var i = 0; i < 12; i++) {
348+
fileContents += String.fromCharCode(ret[i]);
349+
}
350+
assert(fileContents === 'a=1_b=2_\0\0\0\0');
351+
352+
ret[8] = ':'.charCodeAt(0);
353+
ret[9] = 'x'.charCodeAt(0);
354+
ret[10] = 'y'.charCodeAt(0);
355+
ret[11] = 'z'.charCodeAt(0);
356+
Module.HEAPU8.set(ret, mapped.ptr);
357+
358+
// The WasmFS msync syscall requires a pointer to the mapped memory, while the legacy JS API takes in any Uint8Array
359+
// buffer to write to a file.
360+
#if WASMFS
361+
FS.msync(stream, mapped.ptr, 0, 12, 1 /* MAP_SHARED */);
362+
363+
var ex;
364+
try {
365+
FS.munmap(mapped.ptr, 4);
366+
} catch (err) {
367+
ex = err;
368+
}
369+
assert(ex.name === "ErrnoError" && ex.errno === 28 /* EINVAL */);
370+
371+
FS.munmap(mapped.ptr, 12);
372+
373+
// WasmFS correctly handles unmapping, while the legacy JS API does not.
374+
try {
375+
FS.msync(stream, mapped.ptr, 0, 12, 1 /* MAP_SHARED */);
376+
} catch (err) {
377+
ex = err;
378+
}
379+
assert(ex.name === "ErrnoError" && ex.errno === 28 /* EINVAL */);
380+
#else
381+
FS.msync(stream, new Uint8Array(ret), 0, 12, 1 /* MAP_SHARED */);
382+
FS.munmap(stream);
383+
#endif
384+
);
385+
386+
FILE *fptr = fopen("mmaptest", "r");
387+
char res[13];
388+
fgets(res, 13, fptr);
389+
assert(strcmp(res, "a=1_b=2_:xyz") == 0);
390+
391+
remove("mmaptest");
392+
}
393+
336394
void test_fs_mkdirTree() {
337395
EM_ASM(
338396
FS.mkdirTree("/test1/test2/test3");
@@ -413,6 +471,10 @@ int main() {
413471
test_fs_mknod();
414472
test_fs_allocate();
415473
test_fs_truncate();
474+
#if WASMFS
475+
// TODO: Fix legacy API FS.mmap bug involving emscripten_builtin_memalign
476+
test_fs_mmap();
477+
#endif
416478
test_fs_mkdirTree();
417479
test_fs_utime();
418480

0 commit comments

Comments
 (0)