Skip to content

Commit

Permalink
WasmFS JS API: Implement mmap (#20019)
Browse files Browse the repository at this point in the history
This PR implements mmap, msync, and munmap, and adds JS API tests to test_fs_js_api.
  • Loading branch information
jameshu15869 authored Aug 23, 2023
1 parent 9c0efe9 commit ac85c42
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 4 deletions.
4 changes: 4 additions & 0 deletions emcc.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -2358,6 +2358,7 @@ def phase_linker_setup(options, state, newargs):
# included, as the entire JS library can refer to things that require
# these exports.)
settings.REQUIRED_EXPORTS += [
'emscripten_builtin_memalign',
'wasmfs_create_file',
'_wasmfs_mount',
'_wasmfs_unmount',
Expand All @@ -2374,6 +2375,9 @@ def phase_linker_setup(options, state, newargs):
'_wasmfs_chdir',
'_wasmfs_mknod',
'_wasmfs_rmdir',
'_wasmfs_mmap',
'_wasmfs_munmap',
'_wasmfs_msync',
'_wasmfs_read',
'_wasmfs_pread',
'_wasmfs_symlink',
Expand Down
16 changes: 13 additions & 3 deletions src/library_wasmfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,19 @@ FS.createPreloadedFile = FS_createPreloadedFile;
allocate(stream, offset, length) {
return FS.handleError(__wasmfs_allocate(stream.fd, {{{ splitI64('offset') }}}, {{{ splitI64('length') }}}));
},
// TODO: mmap
// TODO: msync
// TODO: munmap
mmap: (stream, length, offset, prot, flags) => {
var buf = FS.handleError(__wasmfs_mmap(length, prot, flags, stream.fd, {{{ splitI64('offset') }}}));
return { ptr: buf, allocated: true };
},
// offset is passed to msync to maintain backwards compatability with the legacy JS API but is not used by WasmFS.
msync: (stream, bufferPtr, offset, length, mmapFlags) => {
assert(offset === 0);
// TODO: assert that stream has the fd corresponding to the mapped buffer (bufferPtr).
return FS.handleError(__wasmfs_msync(bufferPtr, length, mmapFlags));
},
munmap: (addr, length) => (
FS.handleError(__wasmfs_munmap(addr, length))
),
writeFile: (path, data) => withStackSave(() => {
var pathBuffer = stringToUTF8OnStack(path);
if (typeof data == 'string') {
Expand Down
14 changes: 13 additions & 1 deletion system/lib/wasmfs/js_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,18 @@ int _wasmfs_ftruncate(int fd, off_t length) {

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

int _wasmfs_mmap(size_t length, int prot, int flags, int fd, off_t offset) {
return __syscall_mmap2(0, length, prot, flags, fd, offset);
}

int _wasmfs_msync(void* addr, size_t length, int flags) {
return __syscall_msync((intptr_t)addr, length, flags);
}

int _wasmfs_munmap(void* addr, size_t length) {
return __syscall_munmap((intptr_t)addr, length);
}

int _wasmfs_utime(char* path, long atime_ms, long mtime_ms) {
struct timespec times[2];
times[0].tv_sec = atime_ms / 1000;
Expand All @@ -261,7 +273,7 @@ int _wasmfs_utime(char* path, long atime_ms, long mtime_ms) {
times[1].tv_nsec = (mtime_ms % 1000) * 1000000;

return __syscall_utimensat(AT_FDCWD, (intptr_t)path, (intptr_t)times, 0);
};
}

int _wasmfs_stat(char* path, struct stat* statBuf) {
return __syscall_stat64((intptr_t)path, (intptr_t)statBuf);
Expand Down
62 changes: 62 additions & 0 deletions test/fs/test_fs_js_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <emscripten/emscripten.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
Expand Down Expand Up @@ -333,6 +334,63 @@ void test_fs_truncate() {
remove("truncatetest");
}

void test_fs_mmap() {
EM_ASM(
FS.writeFile('mmaptest', 'a=1_b=2_');

var stream = FS.open('mmaptest', 'r+');
assert(stream);

var mapped = FS.mmap(stream, 12, 0, 1 | 2 /* PROT_READ | PROT_WRITE */, 1 /* MAP_SHARED */);
var ret = new Uint8Array(Module.HEAPU8.subarray(mapped.ptr, mapped.ptr + 12));
var fileContents = "";
for (var i = 0; i < 12; i++) {
fileContents += String.fromCharCode(ret[i]);
}
assert(fileContents === 'a=1_b=2_\0\0\0\0');

ret[8] = ':'.charCodeAt(0);
ret[9] = 'x'.charCodeAt(0);
ret[10] = 'y'.charCodeAt(0);
ret[11] = 'z'.charCodeAt(0);
Module.HEAPU8.set(ret, mapped.ptr);

// The WasmFS msync syscall requires a pointer to the mapped memory, while the legacy JS API takes in any Uint8Array
// buffer to write to a file.
#if WASMFS
FS.msync(stream, mapped.ptr, 0, 12, 1 /* MAP_SHARED */);

var ex;
try {
FS.munmap(mapped.ptr, 4);
} catch (err) {
ex = err;
}
assert(ex.name === "ErrnoError" && ex.errno === 28 /* EINVAL */);

FS.munmap(mapped.ptr, 12);

// WasmFS correctly handles unmapping, while the legacy JS API does not.
try {
FS.msync(stream, mapped.ptr, 0, 12, 1 /* MAP_SHARED */);
} catch (err) {
ex = err;
}
assert(ex.name === "ErrnoError" && ex.errno === 28 /* EINVAL */);
#else
FS.msync(stream, new Uint8Array(ret), 0, 12, 1 /* MAP_SHARED */);
FS.munmap(stream);
#endif
);

FILE *fptr = fopen("mmaptest", "r");
char res[13];
fgets(res, 13, fptr);
assert(strcmp(res, "a=1_b=2_:xyz") == 0);

remove("mmaptest");
}

void test_fs_mkdirTree() {
EM_ASM(
FS.mkdirTree("/test1/test2/test3");
Expand Down Expand Up @@ -413,6 +471,10 @@ int main() {
test_fs_mknod();
test_fs_allocate();
test_fs_truncate();
#if WASMFS
// TODO: Fix legacy API FS.mmap bug involving emscripten_builtin_memalign
test_fs_mmap();
#endif
test_fs_mkdirTree();
test_fs_utime();

Expand Down

0 comments on commit ac85c42

Please sign in to comment.