Skip to content

Commit a703ee7

Browse files
authored
[WasmFS] Begin JS API integration (#16068)
This adds a few initial JS API functions. These will be enough to enable a test in 2-3 PRs, and so no new test is added here. The policy for this API is the same as the old FS: By default WasmFS includes the JS API parts that it itself needs, and nothing more. If the user provides FORCE_FILESYSTEM then the entire JS API is included. (As WasmFS uses less JS, this flag may end up used more in practice, I'm not sure. But I thought it best not to add a new flag here and keep the existing convention.) Also rename the existing read_file JS API support function for consistency, and move it to the new location. No changes to the function's contents.
1 parent 16ede16 commit a703ee7

File tree

5 files changed

+127
-42
lines changed

5 files changed

+127
-42
lines changed

emcc.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1875,7 +1875,17 @@ def default_setting(name, new_default):
18751875
settings.FILESYSTEM = 0
18761876
settings.SYSCALLS_REQUIRE_FILESYSTEM = 0
18771877
settings.JS_LIBRARIES.append((0, 'library_wasmfs.js'))
1878-
settings.REQUIRED_EXPORTS += ['emscripten_wasmfs_read_file']
1878+
settings.REQUIRED_EXPORTS += ['_wasmfs_read_file']
1879+
if settings.FORCE_FILESYSTEM:
1880+
# Add exports for the JS API. Like the old JS FS, WasmFS by default
1881+
# includes just what JS parts it actually needs, and FORCE_FILESYSTEM is
1882+
# required to force all of it to be included if the user wants to use the
1883+
# JS API directly.
1884+
settings.REQUIRED_EXPORTS += [
1885+
'_wasmfs_write_file',
1886+
'_wasmfs_mkdir',
1887+
'_wasmfs_chdir',
1888+
]
18791889

18801890
# Explicitly drop linking in a malloc implementation if program is not using any dynamic allocation calls.
18811891
if not settings.USES_DYNAMIC_ALLOC:

src/library_wasmfs.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ var WasmfsLibrary = {
6666
var pathName = allocateUTF8(path);
6767

6868
// Copy the file into a JS buffer on the heap.
69-
var buf = _emscripten_wasmfs_read_file(pathName);
69+
var buf = __wasmfs_read_file(pathName);
7070
// The integer length is returned in the first 8 bytes of the buffer.
7171
var length = {{{ makeGetValue('buf', '0', 'i64') }}};
7272

@@ -86,7 +86,25 @@ var WasmfsLibrary = {
8686
// User code should not be using FS.cwd().
8787
// For file preloading, cwd should be '/' to begin with.
8888
return '/';
89-
}
89+
},
90+
91+
#if FORCE_FILESYSTEM
92+
// Full JS API support
93+
mkdir: function(path) {
94+
var buffer = allocateUTF8OnStack(path);
95+
__wasmfs_mkdir(buffer);
96+
},
97+
chdir: function(path) {
98+
var buffer = allocateUTF8OnStack(path);
99+
return __wasmfs_chdir(buffer);
100+
},
101+
writeFile: function(path, data) {
102+
var pathBuffer = allocateUTF8OnStack(path);
103+
var dataBuffer = allocate(data);
104+
__wasmfs_write_file(pathBuffer, dataBuffer, data.length);
105+
_free(dataBuffer);
106+
},
107+
#endif
90108
},
91109
_wasmfs_get_num_preloaded_files__deps: ['$wasmFS$preloadedFiles'],
92110
_wasmfs_get_num_preloaded_files: function() {

system/lib/wasmfs/js_api.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2022 The Emscripten Authors. All rights reserved.
2+
// Emscripten is available under two separate licenses, the MIT license and the
3+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4+
// found in the LICENSE file.
5+
6+
#include <syscall_arch.h>
7+
#include <unistd.h>
8+
9+
#include "file.h"
10+
11+
using namespace wasmfs;
12+
13+
extern "C" {
14+
15+
__wasi_fd_t wasmfs_create_file(char* pathname, mode_t mode, backend_t backend);
16+
17+
// Copy the file specified by the pathname into JS.
18+
// Return a pointer to the JS buffer in HEAPU8.
19+
// The buffer will also contain the file length.
20+
// Caller must free the returned pointer.
21+
void* _wasmfs_read_file(char* path) {
22+
struct stat file;
23+
int err = 0;
24+
err = stat(path, &file);
25+
if (err < 0) {
26+
emscripten_console_error("Fatal error in FS.readFile");
27+
abort();
28+
}
29+
30+
// The function will return a pointer to a buffer with the file length in the
31+
// first 8 bytes. The remaining bytes will contain the buffer contents. This
32+
// allows the caller to use HEAPU8.subarray(buf + 8, buf + 8 + length).
33+
off_t size = file.st_size;
34+
uint8_t* result = (uint8_t*)malloc((size + sizeof(size)));
35+
*(uint32_t*)result = size;
36+
37+
int fd = open(path, O_RDONLY);
38+
if (fd < 0) {
39+
emscripten_console_error("Fatal error in FS.readFile");
40+
abort();
41+
}
42+
int numRead = pread(fd, result + sizeof(size), size, 0);
43+
// TODO: Generalize this so that it is thread-proof.
44+
// Must guarantee that the file size has not changed by the time it is read.
45+
assert(numRead == size);
46+
err = close(fd);
47+
if (err < 0) {
48+
emscripten_console_error("Fatal error in FS.readFile");
49+
abort();
50+
}
51+
52+
return result;
53+
}
54+
55+
// Writes to a file, possibly creating it, and returns the number of bytes
56+
// written successfully.
57+
long _wasmfs_write_file(char* pathname, char* data, size_t data_size) {
58+
auto pathParts = splitPath(pathname);
59+
60+
long err;
61+
auto parsedPath = getParsedPath(pathParts, err);
62+
if (!parsedPath.parent) {
63+
return 0;
64+
}
65+
66+
if (!parsedPath.child) {
67+
// Create a file here.
68+
wasmfs_create_file(
69+
pathname, O_WRONLY, parsedPath.parent->getParent()->getBackend());
70+
} else if (!parsedPath.child->is<DataFile>()) {
71+
// There is something here but it isn't a data file.
72+
return 0;
73+
}
74+
75+
auto child = parsedPath.parent->getEntry(pathParts.back());
76+
auto dataFile = child->dynCast<DataFile>();
77+
78+
auto result = dataFile->locked().write((uint8_t*)data, data_size, 0);
79+
if (result != __WASI_ERRNO_SUCCESS) {
80+
return 0;
81+
}
82+
return data_size;
83+
}
84+
85+
long _wasmfs_mkdir(char* path, long mode) {
86+
return __syscall_mkdir((long)path, mode);
87+
}
88+
89+
long _wasmfs_chdir(char* path) { return __syscall_chdir((long)path); }
90+
91+
}

system/lib/wasmfs/syscalls.cpp

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -34,44 +34,6 @@ extern "C" {
3434

3535
using namespace wasmfs;
3636

37-
// Copy the file specified by the pathname into JS.
38-
// Return a pointer to the JS buffer in HEAPU8.
39-
// The buffer will also contain the file length.
40-
// Caller must free the returned pointer.
41-
void* emscripten_wasmfs_read_file(char* path) {
42-
struct stat file;
43-
int err = 0;
44-
err = stat(path, &file);
45-
if (err < 0) {
46-
emscripten_console_error("Fatal error in FS.readFile");
47-
abort();
48-
}
49-
50-
// The function will return a pointer to a buffer with the file length in the
51-
// first 8 bytes. The remaining bytes will contain the buffer contents. This
52-
// allows the caller to use HEAPU8.subarray(buf + 8, buf + 8 + length).
53-
off_t size = file.st_size;
54-
uint8_t* result = (uint8_t*)malloc((size + sizeof(size)));
55-
*(uint32_t*)result = size;
56-
57-
int fd = open(path, O_RDONLY);
58-
if (fd < 0) {
59-
emscripten_console_error("Fatal error in FS.readFile");
60-
abort();
61-
}
62-
int numRead = pread(fd, result + sizeof(size), size, 0);
63-
// TODO: Generalize this so that it is thread-proof.
64-
// Must guarantee that the file size has not changed by the time it is read.
65-
assert(numRead == size);
66-
err = close(fd);
67-
if (err < 0) {
68-
emscripten_console_error("Fatal error in FS.readFile");
69-
abort();
70-
}
71-
72-
return result;
73-
}
74-
7537
long __syscall_dup3(long oldfd, long newfd, long flags) {
7638
auto fileTable = wasmFS.getLockedFileTable();
7739

@@ -926,4 +888,5 @@ long __syscall_rename(long old_path, long new_path) {
926888

927889
return 0;
928890
}
891+
929892
}

tools/system_libs.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1428,7 +1428,10 @@ class libwasmfs(MTLibrary, DebugLibrary, AsanInstrumentedLibrary):
14281428
def get_files(self):
14291429
return files_in_path(
14301430
path='system/lib/wasmfs',
1431-
filenames=['syscalls.cpp', 'file_table.cpp', 'file.cpp', 'wasmfs.cpp', 'streams.cpp', 'memory_file.cpp', 'memory_file_backend.cpp', 'js_file_backend.cpp', 'proxied_file_backend.cpp'])
1431+
filenames=['syscalls.cpp', 'file_table.cpp', 'file.cpp', 'wasmfs.cpp',
1432+
'streams.cpp', 'memory_file.cpp', 'memory_file_backend.cpp',
1433+
'js_file_backend.cpp', 'proxied_file_backend.cpp',
1434+
'js_api.cpp'])
14321435

14331436
def can_use(self):
14341437
return settings.WASMFS

0 commit comments

Comments
 (0)