From 36bb44f8e87501238ab4810427350cf092b8a1cd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 12:36:29 -0800 Subject: [PATCH 01/61] start --- src/library_wasmfs.js | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index b60fffdaa844e..f7f0387373b86 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -1,13 +1,9 @@ var WasmfsLibrary = { - $wasmFS$JSMemoryFiles : [], - $wasmFS$JSMemoryFreeList: [], $wasmFS$preloadedFiles: [], $wasmFS$preloadedDirs: [], $FS__deps: [ '$wasmFS$preloadedFiles', '$wasmFS$preloadedDirs', - '$wasmFS$JSMemoryFiles', - '$wasmFS$JSMemoryFreeList', '$asyncLoad', #if !MINIMAL_RUNTIME // TODO: when preload-plugins are not used, we do not need this. @@ -145,6 +141,21 @@ var WasmfsLibrary = { var len = lengthBytesUTF8(s) + 1; stringToUTF8(s, fileNameBuffer, len); }, + _wasmfs_get_preloaded_file_size: function(index) { + return wasmFS$preloadedFiles[index].fileData.length; + }, + _wasmfs_copy_preloaded_file_data: function(index, buffer) { + HEAPU8.set(wasmFS$preloadedFiles[index].fileData, buffer); + }, + + // JSFile backend + + $wasmFS$JSMemoryFiles : [], + $wasmFS$JSMemoryFreeList: [], + + _wasmfs_write_js_file__deps: [ + '$wasmFS$JSMemoryFiles', + ], _wasmfs_write_js_file: function(index, buffer, length, offset) { try { if (!wasmFS$JSMemoryFiles[index]) { @@ -166,6 +177,9 @@ var WasmfsLibrary = { return {{{ cDefine('EIO') }}}; } }, + _wasmfs_read_js_file__deps: [ + '$wasmFS$JSMemoryFiles', + ], _wasmfs_read_js_file: function(index, buffer, length, offset) { try { HEAPU8.set(wasmFS$JSMemoryFiles[index].subarray(offset, offset + length), buffer); @@ -174,9 +188,16 @@ var WasmfsLibrary = { return {{{ cDefine('EIO') }}}; } }, + _wasmfs_get_js_file_size__deps: [ + '$wasmFS$JSMemoryFiles', + ], _wasmfs_get_js_file_size: function(index) { return wasmFS$JSMemoryFiles[index] ? wasmFS$JSMemoryFiles[index].length : 0; }, + _wasmfs_create_js_file__deps: [ + '$wasmFS$JSMemoryFiles', + '$wasmFS$JSMemoryFreeList', + ], _wasmfs_create_js_file: function() { // Find a free entry in the $wasmFS$JSMemoryFreeList or append a new entry to // wasmFS$JSMemoryFiles. @@ -188,17 +209,15 @@ var WasmfsLibrary = { wasmFS$JSMemoryFiles.push(null); return wasmFS$JSMemoryFiles.length - 1; }, + _wasmfs_remove_js_file__deps: [ + '$wasmFS$JSMemoryFiles', + '$wasmFS$JSMemoryFreeList', + ], _wasmfs_remove_js_file: function(index) { wasmFS$JSMemoryFiles[index] = null; // Add the index to the free list. wasmFS$JSMemoryFreeList.push(index); }, - _wasmfs_get_preloaded_file_size: function(index) { - return wasmFS$preloadedFiles[index].fileData.length; - }, - _wasmfs_copy_preloaded_file_data: function(index, buffer) { - HEAPU8.set(wasmFS$preloadedFiles[index].fileData, buffer); - }, } mergeInto(LibraryManager.library, WasmfsLibrary); From 91326ec6ec78f97ee34c050a85310a338e6c54bc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 12:58:35 -0800 Subject: [PATCH 02/61] work [ci skip] --- src/library_wasmfs.js | 117 +++++++++++++------------- system/lib/wasmfs/js_file_backend.cpp | 66 +++++++++------ 2 files changed, 98 insertions(+), 85 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index f7f0387373b86..622f15a9b7c90 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -148,75 +148,72 @@ var WasmfsLibrary = { HEAPU8.set(wasmFS$preloadedFiles[index].fileData, buffer); }, + // Backend support. WasmFSBackends will contain a mapping of backend IDs to + // the JS code that implements them. This is the JS side of the JSImpl class + // in C++. + WasmFSBackends: {}, + // JSFile backend $wasmFS$JSMemoryFiles : [], $wasmFS$JSMemoryFreeList: [], - _wasmfs_write_js_file__deps: [ + _wasmfs_backend_add_jsfile__deps: [ + '$WasmFSBackends', '$wasmFS$JSMemoryFiles', + '$wasmFS$JSMemoryFreeList', ], - _wasmfs_write_js_file: function(index, buffer, length, offset) { - try { - if (!wasmFS$JSMemoryFiles[index]) { - // Initialize typed array on first write operation. - wasmFS$JSMemoryFiles[index] = new Uint8Array(offset + length); - } + _wasmfs_backend_add_jsfile: function(backend) { + WasmFSBackends[backend] = { + constructor: function() { + // Find a free entry in the $wasmFS$JSMemoryFreeList or append a new entry to + // wasmFS$JSMemoryFiles. + if (wasmFS$JSMemoryFreeList.length) { + // Pop off the top of the free list. + var index = wasmFS$JSMemoryFreeList.pop(); + return index; + } + wasmFS$JSMemoryFiles.push(null); + return wasmFS$JSMemoryFiles.length - 1; + }, + destructor: function(index) { + wasmFS$JSMemoryFiles[index] = null; + // Add the index to the free list. + wasmFS$JSMemoryFreeList.push(index); + }, + write: function(index, buffer, length, offset) { + try { + if (!wasmFS$JSMemoryFiles[index]) { + // Initialize typed array on first write operation. + wasmFS$JSMemoryFiles[index] = new Uint8Array(offset + length); + } - if (offset + length > wasmFS$JSMemoryFiles[index].length) { - // Resize the typed array if the length of the write buffer exceeds its capacity. - var oldContents = wasmFS$JSMemoryFiles[index]; - var newContents = new Uint8Array(offset + length); - newContents.set(oldContents); - wasmFS$JSMemoryFiles[index] = newContents; - } + if (offset + length > wasmFS$JSMemoryFiles[index].length) { + // Resize the typed array if the length of the write buffer exceeds its capacity. + var oldContents = wasmFS$JSMemoryFiles[index]; + var newContents = new Uint8Array(offset + length); + newContents.set(oldContents); + wasmFS$JSMemoryFiles[index] = newContents; + } - wasmFS$JSMemoryFiles[index].set(HEAPU8.subarray(buffer, buffer + length), offset); - return 0; - } catch (err) { - return {{{ cDefine('EIO') }}}; - } - }, - _wasmfs_read_js_file__deps: [ - '$wasmFS$JSMemoryFiles', - ], - _wasmfs_read_js_file: function(index, buffer, length, offset) { - try { - HEAPU8.set(wasmFS$JSMemoryFiles[index].subarray(offset, offset + length), buffer); - return 0; - } catch (err) { - return {{{ cDefine('EIO') }}}; - } - }, - _wasmfs_get_js_file_size__deps: [ - '$wasmFS$JSMemoryFiles', - ], - _wasmfs_get_js_file_size: function(index) { - return wasmFS$JSMemoryFiles[index] ? wasmFS$JSMemoryFiles[index].length : 0; - }, - _wasmfs_create_js_file__deps: [ - '$wasmFS$JSMemoryFiles', - '$wasmFS$JSMemoryFreeList', - ], - _wasmfs_create_js_file: function() { - // Find a free entry in the $wasmFS$JSMemoryFreeList or append a new entry to - // wasmFS$JSMemoryFiles. - if (wasmFS$JSMemoryFreeList.length) { - // Pop off the top of the free list. - var index = wasmFS$JSMemoryFreeList.pop(); - return index; - } - wasmFS$JSMemoryFiles.push(null); - return wasmFS$JSMemoryFiles.length - 1; - }, - _wasmfs_remove_js_file__deps: [ - '$wasmFS$JSMemoryFiles', - '$wasmFS$JSMemoryFreeList', - ], - _wasmfs_remove_js_file: function(index) { - wasmFS$JSMemoryFiles[index] = null; - // Add the index to the free list. - wasmFS$JSMemoryFreeList.push(index); + wasmFS$JSMemoryFiles[index].set(HEAPU8.subarray(buffer, buffer + length), offset); + return 0; + } catch (err) { + return {{{ cDefine('EIO') }}}; + } + }, + read: function(index, buffer, length, offset) { + try { + HEAPU8.set(wasmFS$JSMemoryFiles[index].subarray(offset, offset + length), buffer); + return 0; + } catch (err) { + return {{{ cDefine('EIO') }}}; + } + }, + getSize: function(index) { + return wasmFS$JSMemoryFiles[index] ? wasmFS$JSMemoryFiles[index].length : 0; + }, + }; }, } diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index 6915f4dba1a10..6f2d971074d1a 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -13,56 +13,70 @@ using js_index_t = uint32_t; extern "C" { -int _wasmfs_write_js_file(js_index_t index, - const uint8_t* buffer, - size_t length, - off_t offset); -int _wasmfs_read_js_file(js_index_t index, +// JSImpl API (see below for overview). +int _wasmfs_jsimpl_write(backend_t backend, + js_index_t index, const uint8_t* buffer, size_t length, off_t offset); -int _wasmfs_get_js_file_size(js_index_t index); -int _wasmfs_create_js_file(); -void _wasmfs_remove_js_file(js_index_t index); +int _wasmfs_jsimpl_read(backend_t backend, + js_index_t index, + const uint8_t* buffer, + size_t length, + off_t offset); +int _wasmfs_jsimpl_get_size(backend_t backend, js_index_t index); +int _wasmfs_jsimpl_create(backend_t backend); +void _wasmfs_jsimpl_remove(backend_t backend, js_index_t index); + +// Backends. +void _wasmfs_init_jsfile_backend_js(); } + namespace wasmfs { -// This class describes a file that lives in JS Memory -class JSFile : public DataFile { - // This index indicates the location of the JS File in the backing JS array. - js_index_t index; +// A "JSImplFile" is a file that is implemented by JS code. Which particular +// JS code handles it is indicated by a pointer to the backend. The JS code on +// the other side connects that pointer to the actual JS code, basically adding +// a layer of indirection that way. This allows a single C++ class here to have +// multiple JS implementations, which makes it easy to write new JS backends +// without C++ boilerplate. +// +// Each file operation in the _wasmfs_jsimpl_* APIs that we call from here take +// the backend and a pointer to this file itself. Those allow the JS to identify +// both the backend and the particular file. TODO: We could use dense indexes +// instead of pointers, which cause JS to use a map and not an array. +class JSImplFile : public DataFile { + js_index_t getIndex() { + static_assert(sizeof(this) == sizeof(js_index_t), "TODO: wasm64"); + return this; + } - // JSFiles will write from a Wasm Memory buffer into the backing JS array. __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { - return _wasmfs_write_js_file(index, buf, len, offset); + return _wasmfs_jsimpl_write(getBackend(), getIndex(), buf, len, offset); } - // JSFiles will read from the backing JS array into a Wasm Memory buffer. __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { // The caller should have already checked that the offset + len does // not exceed the file's size. assert(offset + len <= getSize()); - return _wasmfs_read_js_file(index, buf, len, offset); + return _wasmfs_jsimpl_read(getBackend(), getIndex(), buf, len, offset); } void flush() override {} - // The size of the JSFile is defined as the length of the backing JS array. - size_t getSize() override { return _wasmfs_get_js_file_size(index); } + size_t getSize() override { return _wasmfs_jsimpl_get_size(getBackend(), getIndex()); } public: - JSFile(mode_t mode, backend_t backend) : DataFile(mode, backend) { - // Create a new file in the backing JS array and store its index. - index = _wasmfs_create_js_file(); + JSImplFile(mode_t mode, backend_t backend) : DataFile(mode, backend) { + _wasmfs_jsimpl_constructor(getBackend()); } - // Remove the typed array file contents in the backing JS array. - ~JSFile() { _wasmfs_remove_js_file(index); } + ~JSImplFile() { _wasmfs_jsimpl_destructor(getBackend(), getIndex()); } }; +// This backend's files reside in JS Memory. class JSFileBackend : public Backend { - public: std::shared_ptr createFile(mode_t mode) override { return std::make_shared(mode, this); @@ -71,7 +85,9 @@ class JSFileBackend : public Backend { // This function is exposed to users to instantiate a new JSBackend. extern "C" backend_t wasmfs_create_js_file_backend() { - return wasmFS.addBackend(std::make_unique()); + backend_t backend = wasmFS.addBackend(std::make_unique()); + _wasmfs_backend_add_jsfile(backend); + return backend;; } } // namespace wasmfs From 91bf5120a109ef27490dcea25106040a8d47b178 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 13:05:07 -0800 Subject: [PATCH 03/61] work [ci skip] --- src/library_wasmfs.js | 81 +++++++++++++++++---------- system/lib/wasmfs/js_file_backend.cpp | 2 +- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 622f15a9b7c90..e812e8e72c513 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -150,71 +150,92 @@ var WasmfsLibrary = { // Backend support. WasmFSBackends will contain a mapping of backend IDs to // the JS code that implements them. This is the JS side of the JSImpl class - // in C++. + // in C++, together with the js_impl calls defined right after it. WasmFSBackends: {}, + _wasmfs_jsimpl_constructor: function(backend, file) { +#if ASSERTIONS + assert(WasmFSBackends[backend]); +#endif + return WasmFSBackends[backend].constructor(file); + }, + + _wasmfs_jsimpl_destructor: function(backend, file) { +#if ASSERTIONS + assert(WasmFSBackends[backend]); +#endif + return WasmFSBackends[backend].destructor(file); + }, + + _wasmfs_jsimpl_write: function(backend, file, buffer, length, offset) { +#if ASSERTIONS + assert(WasmFSBackends[backend]); +#endif + return WasmFSBackends[backend].write(file, buffer, length, offset); + }, + + _wasmfs_jsimpl_read: function(backend, file, buffer, length, offset) { +#if ASSERTIONS + assert(WasmFSBackends[backend]); +#endif + return WasmFSBackends[backend].read(file, buffer, length, offset); + }, + + _wasmfs_jsimpl_get_size: function(backend, file) { +#if ASSERTIONS + assert(WasmFSBackends[backend]); +#endif + return WasmFSBackends[backend].getSize(file); + }, + // JSFile backend - $wasmFS$JSMemoryFiles : [], - $wasmFS$JSMemoryFreeList: [], + $wasmFS$JSMemoryFiles : {}, _wasmfs_backend_add_jsfile__deps: [ '$WasmFSBackends', '$wasmFS$JSMemoryFiles', - '$wasmFS$JSMemoryFreeList', ], _wasmfs_backend_add_jsfile: function(backend) { WasmFSBackends[backend] = { - constructor: function() { - // Find a free entry in the $wasmFS$JSMemoryFreeList or append a new entry to - // wasmFS$JSMemoryFiles. - if (wasmFS$JSMemoryFreeList.length) { - // Pop off the top of the free list. - var index = wasmFS$JSMemoryFreeList.pop(); - return index; - } - wasmFS$JSMemoryFiles.push(null); - return wasmFS$JSMemoryFiles.length - 1; + constructor: function(file) {}, }, - destructor: function(index) { - wasmFS$JSMemoryFiles[index] = null; - // Add the index to the free list. - wasmFS$JSMemoryFreeList.push(index); - }, - write: function(index, buffer, length, offset) { + destructor: function(file) {}, + write: function(file, buffer, length, offset) { try { - if (!wasmFS$JSMemoryFiles[index]) { + if (!wasmFS$JSMemoryFiles[file]) { // Initialize typed array on first write operation. - wasmFS$JSMemoryFiles[index] = new Uint8Array(offset + length); + wasmFS$JSMemoryFiles[file] = new Uint8Array(offset + length); } - if (offset + length > wasmFS$JSMemoryFiles[index].length) { + if (offset + length > wasmFS$JSMemoryFiles[file].length) { // Resize the typed array if the length of the write buffer exceeds its capacity. - var oldContents = wasmFS$JSMemoryFiles[index]; + var oldContents = wasmFS$JSMemoryFiles[file]; var newContents = new Uint8Array(offset + length); newContents.set(oldContents); - wasmFS$JSMemoryFiles[index] = newContents; + wasmFS$JSMemoryFiles[file] = newContents; } - wasmFS$JSMemoryFiles[index].set(HEAPU8.subarray(buffer, buffer + length), offset); + wasmFS$JSMemoryFiles[file].set(HEAPU8.subarray(buffer, buffer + length), offset); return 0; } catch (err) { return {{{ cDefine('EIO') }}}; } }, - read: function(index, buffer, length, offset) { + read: function(file, buffer, length, offset) { try { - HEAPU8.set(wasmFS$JSMemoryFiles[index].subarray(offset, offset + length), buffer); + HEAPU8.set(wasmFS$JSMemoryFiles[file].subarray(offset, offset + length), buffer); return 0; } catch (err) { return {{{ cDefine('EIO') }}}; } }, - getSize: function(index) { - return wasmFS$JSMemoryFiles[index] ? wasmFS$JSMemoryFiles[index].length : 0; + getSize: function(file) { + return wasmFS$JSMemoryFiles[file] ? wasmFS$JSMemoryFiles[file].length : 0; }, }; }, + } mergeInto(LibraryManager.library, WasmfsLibrary); diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index 6f2d971074d1a..6130e2b0e8d39 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -69,7 +69,7 @@ class JSImplFile : public DataFile { public: JSImplFile(mode_t mode, backend_t backend) : DataFile(mode, backend) { - _wasmfs_jsimpl_constructor(getBackend()); + _wasmfs_jsimpl_constructor(getBackend(), getIndex()); } ~JSImplFile() { _wasmfs_jsimpl_destructor(getBackend(), getIndex()); } From 11f82c765d132660f5249410c7675600a7fa2477 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 13:13:36 -0800 Subject: [PATCH 04/61] builds [ci skip] --- src/library_wasmfs.js | 3 +-- system/lib/wasmfs/js_file_backend.cpp | 35 +++++++++++++++------------ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index e812e8e72c513..9e5ef64b6e492 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -190,7 +190,7 @@ var WasmfsLibrary = { // JSFile backend - $wasmFS$JSMemoryFiles : {}, + $wasmFS$JSMemoryFiles: {}, _wasmfs_backend_add_jsfile__deps: [ '$WasmFSBackends', @@ -199,7 +199,6 @@ var WasmfsLibrary = { _wasmfs_backend_add_jsfile: function(backend) { WasmFSBackends[backend] = { constructor: function(file) {}, - }, destructor: function(file) {}, write: function(file, buffer, length, offset) { try { diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index 6130e2b0e8d39..378cb557c7afd 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -14,22 +14,22 @@ using js_index_t = uint32_t; extern "C" { // JSImpl API (see below for overview). -int _wasmfs_jsimpl_write(backend_t backend, +int _wasmfs_jsimpl_write(js_index_t backend, js_index_t index, const uint8_t* buffer, size_t length, off_t offset); -int _wasmfs_jsimpl_read(backend_t backend, +int _wasmfs_jsimpl_read(js_index_t backend, js_index_t index, const uint8_t* buffer, size_t length, off_t offset); -int _wasmfs_jsimpl_get_size(backend_t backend, js_index_t index); -int _wasmfs_jsimpl_create(backend_t backend); -void _wasmfs_jsimpl_remove(backend_t backend, js_index_t index); +int _wasmfs_jsimpl_get_size(js_index_t backend, js_index_t index); +void _wasmfs_jsimpl_constructor(js_index_t backend, js_index_t index); +void _wasmfs_jsimpl_destructor(js_index_t backend, js_index_t index); // Backends. -void _wasmfs_init_jsfile_backend_js(); +void _wasmfs_backend_add_jsfile(js_index_t); } @@ -47,46 +47,51 @@ namespace wasmfs { // both the backend and the particular file. TODO: We could use dense indexes // instead of pointers, which cause JS to use a map and not an array. class JSImplFile : public DataFile { - js_index_t getIndex() { + js_index_t getBackendIndex() { + static_assert(sizeof(backend_t) == sizeof(js_index_t), "TODO: wasm64"); + return js_index_t(getBackend()); + } + + js_index_t getFileIndex() { static_assert(sizeof(this) == sizeof(js_index_t), "TODO: wasm64"); - return this; + return js_index_t(this); } __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { - return _wasmfs_jsimpl_write(getBackend(), getIndex(), buf, len, offset); + return _wasmfs_jsimpl_write(getBackendIndex(), getFileIndex(), buf, len, offset); } __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { // The caller should have already checked that the offset + len does // not exceed the file's size. assert(offset + len <= getSize()); - return _wasmfs_jsimpl_read(getBackend(), getIndex(), buf, len, offset); + return _wasmfs_jsimpl_read(getBackendIndex(), getFileIndex(), buf, len, offset); } void flush() override {} - size_t getSize() override { return _wasmfs_jsimpl_get_size(getBackend(), getIndex()); } + size_t getSize() override { return _wasmfs_jsimpl_get_size(getBackendIndex(), getFileIndex()); } public: JSImplFile(mode_t mode, backend_t backend) : DataFile(mode, backend) { - _wasmfs_jsimpl_constructor(getBackend(), getIndex()); + _wasmfs_jsimpl_constructor(getBackendIndex(), getFileIndex()); } - ~JSImplFile() { _wasmfs_jsimpl_destructor(getBackend(), getIndex()); } + ~JSImplFile() { _wasmfs_jsimpl_destructor(getBackendIndex(), getFileIndex()); } }; // This backend's files reside in JS Memory. class JSFileBackend : public Backend { public: std::shared_ptr createFile(mode_t mode) override { - return std::make_shared(mode, this); + return std::make_shared(mode, this); } }; // This function is exposed to users to instantiate a new JSBackend. extern "C" backend_t wasmfs_create_js_file_backend() { backend_t backend = wasmFS.addBackend(std::make_unique()); - _wasmfs_backend_add_jsfile(backend); + _wasmfs_backend_add_jsfile(js_index_t(backend)); return backend;; } From fabf251aca000afa743cf52926635f5aab46d6a4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 13:19:38 -0800 Subject: [PATCH 05/61] test passes [ci skip] --- src/library_wasmfs.js | 28 +++++++++++++-------------- system/lib/wasmfs/js_file_backend.cpp | 5 ++++- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 9e5ef64b6e492..140f0b3502dc0 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -148,44 +148,44 @@ var WasmfsLibrary = { HEAPU8.set(wasmFS$preloadedFiles[index].fileData, buffer); }, - // Backend support. WasmFSBackends will contain a mapping of backend IDs to + // Backend support. wasmFS$backends will contain a mapping of backend IDs to // the JS code that implements them. This is the JS side of the JSImpl class // in C++, together with the js_impl calls defined right after it. - WasmFSBackends: {}, + $wasmFS$backends: {}, _wasmfs_jsimpl_constructor: function(backend, file) { #if ASSERTIONS - assert(WasmFSBackends[backend]); + assert(wasmFS$backends[backend]); #endif - return WasmFSBackends[backend].constructor(file); + return wasmFS$backends[backend].constructor(file); }, _wasmfs_jsimpl_destructor: function(backend, file) { #if ASSERTIONS - assert(WasmFSBackends[backend]); + assert(wasmFS$backends[backend]); #endif - return WasmFSBackends[backend].destructor(file); + return wasmFS$backends[backend].destructor(file); }, _wasmfs_jsimpl_write: function(backend, file, buffer, length, offset) { #if ASSERTIONS - assert(WasmFSBackends[backend]); + assert(wasmFS$backends[backend]); #endif - return WasmFSBackends[backend].write(file, buffer, length, offset); + return wasmFS$backends[backend].write(file, buffer, length, offset); }, _wasmfs_jsimpl_read: function(backend, file, buffer, length, offset) { #if ASSERTIONS - assert(WasmFSBackends[backend]); + assert(wasmFS$backends[backend]); #endif - return WasmFSBackends[backend].read(file, buffer, length, offset); + return wasmFS$backends[backend].read(file, buffer, length, offset); }, _wasmfs_jsimpl_get_size: function(backend, file) { #if ASSERTIONS - assert(WasmFSBackends[backend]); + assert(wasmFS$backends[backend]); #endif - return WasmFSBackends[backend].getSize(file); + return wasmFS$backends[backend].getSize(file); }, // JSFile backend @@ -193,11 +193,11 @@ var WasmfsLibrary = { $wasmFS$JSMemoryFiles: {}, _wasmfs_backend_add_jsfile__deps: [ - '$WasmFSBackends', + '$wasmFS$backends', '$wasmFS$JSMemoryFiles', ], _wasmfs_backend_add_jsfile: function(backend) { - WasmFSBackends[backend] = { + wasmFS$backends[backend] = { constructor: function(file) {}, destructor: function(file) {}, write: function(file, buffer, length, offset) { diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index 378cb557c7afd..c27a6742ec8ed 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -10,6 +10,9 @@ #include "backend.h" #include "wasmfs.h" +// TODO rename this file +// TODO add docs for "how to maek a backend" + using js_index_t = uint32_t; extern "C" { @@ -91,7 +94,7 @@ class JSFileBackend : public Backend { // This function is exposed to users to instantiate a new JSBackend. extern "C" backend_t wasmfs_create_js_file_backend() { backend_t backend = wasmFS.addBackend(std::make_unique()); - _wasmfs_backend_add_jsfile(js_index_t(backend)); + _wasmfs_backend_add_jsfile(js_index_t(backend)); // helper to convert with above? return backend;; } From 09aa942c5b25175d6756d276d59656383ba01d55 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 14:45:50 -0800 Subject: [PATCH 06/61] fix --- src/library_wasmfs.js | 56 ++--------------------------------- src/library_wasmfs_js_file.js | 48 ++++++++++++++++++++++++++++++ src/modules.js | 1 + 3 files changed, 52 insertions(+), 53 deletions(-) create mode 100644 src/library_wasmfs_js_file.js diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 140f0b3502dc0..67be73fd19efb 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -1,4 +1,4 @@ -var WasmfsLibrary = { +var WasmFSLibrary = { $wasmFS$preloadedFiles: [], $wasmFS$preloadedDirs: [], $FS__deps: [ @@ -187,58 +187,8 @@ var WasmfsLibrary = { #endif return wasmFS$backends[backend].getSize(file); }, - - // JSFile backend - - $wasmFS$JSMemoryFiles: {}, - - _wasmfs_backend_add_jsfile__deps: [ - '$wasmFS$backends', - '$wasmFS$JSMemoryFiles', - ], - _wasmfs_backend_add_jsfile: function(backend) { - wasmFS$backends[backend] = { - constructor: function(file) {}, - destructor: function(file) {}, - write: function(file, buffer, length, offset) { - try { - if (!wasmFS$JSMemoryFiles[file]) { - // Initialize typed array on first write operation. - wasmFS$JSMemoryFiles[file] = new Uint8Array(offset + length); - } - - if (offset + length > wasmFS$JSMemoryFiles[file].length) { - // Resize the typed array if the length of the write buffer exceeds its capacity. - var oldContents = wasmFS$JSMemoryFiles[file]; - var newContents = new Uint8Array(offset + length); - newContents.set(oldContents); - wasmFS$JSMemoryFiles[file] = newContents; - } - - wasmFS$JSMemoryFiles[file].set(HEAPU8.subarray(buffer, buffer + length), offset); - return 0; - } catch (err) { - return {{{ cDefine('EIO') }}}; - } - }, - read: function(file, buffer, length, offset) { - try { - HEAPU8.set(wasmFS$JSMemoryFiles[file].subarray(offset, offset + length), buffer); - return 0; - } catch (err) { - return {{{ cDefine('EIO') }}}; - } - }, - getSize: function(file) { - return wasmFS$JSMemoryFiles[file] ? wasmFS$JSMemoryFiles[file].length : 0; - }, - }; - }, - } -mergeInto(LibraryManager.library, WasmfsLibrary); +mergeInto(LibraryManager.library, WasmFSLibrary); -if (WASMFS) { - DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('$FS'); -} +DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('$FS'); diff --git a/src/library_wasmfs_js_file.js b/src/library_wasmfs_js_file.js new file mode 100644 index 0000000000000..ec501fdff57e6 --- /dev/null +++ b/src/library_wasmfs_js_file.js @@ -0,0 +1,48 @@ +mergeInto(LibraryManager.library, { + // JSFile backend: Store a file's data in JS. We map File objects in C++ to + // entries here that contain typed arrays. + $wasmFS$JSMemoryFiles: {}, + + _wasmfs_backend_add_jsfile__deps: [ + '$wasmFS$backends', + '$wasmFS$JSMemoryFiles', + ], + _wasmfs_backend_add_jsfile: function(backend) { + wasmFS$backends[backend] = { + constructor: function(file) {}, + destructor: function(file) {}, + write: function(file, buffer, length, offset) { + try { + if (!wasmFS$JSMemoryFiles[file]) { + // Initialize typed array on first write operation. + wasmFS$JSMemoryFiles[file] = new Uint8Array(offset + length); + } + + if (offset + length > wasmFS$JSMemoryFiles[file].length) { + // Resize the typed array if the length of the write buffer exceeds its capacity. + var oldContents = wasmFS$JSMemoryFiles[file]; + var newContents = new Uint8Array(offset + length); + newContents.set(oldContents); + wasmFS$JSMemoryFiles[file] = newContents; + } + + wasmFS$JSMemoryFiles[file].set(HEAPU8.subarray(buffer, buffer + length), offset); + return 0; + } catch (err) { + return {{{ cDefine('EIO') }}}; + } + }, + read: function(file, buffer, length, offset) { + try { + HEAPU8.set(wasmFS$JSMemoryFiles[file].subarray(offset, offset + length), buffer); + return 0; + } catch (err) { + return {{{ cDefine('EIO') }}}; + } + }, + getSize: function(file) { + return wasmFS$JSMemoryFiles[file] ? wasmFS$JSMemoryFiles[file].length : 0; + }, + }; + }, +}); diff --git a/src/modules.js b/src/modules.js index d1f97ff5eefd3..6be6604548909 100644 --- a/src/modules.js +++ b/src/modules.js @@ -95,6 +95,7 @@ global.LibraryManager = { } } else if (WASMFS) { libraries.push('library_wasmfs.js'); + libraries.push('library_wasmfs_js_file.js'); } // Additional JS libraries (without AUTO_JS_LIBRARIES, link to these explicitly via -lxxx.js) From 0df35c156bfab064601542393fc9e4867fc6181b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 14:55:55 -0800 Subject: [PATCH 07/61] fix --- src/library_wasmfs_js_file.js | 4 +- system/lib/wasmfs/js_file_backend.cpp | 76 ++--------------------- system/lib/wasmfs/js_impl_backend.h | 87 +++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 73 deletions(-) create mode 100644 system/lib/wasmfs/js_impl_backend.h diff --git a/src/library_wasmfs_js_file.js b/src/library_wasmfs_js_file.js index ec501fdff57e6..9aae77c13eb8b 100644 --- a/src/library_wasmfs_js_file.js +++ b/src/library_wasmfs_js_file.js @@ -3,11 +3,11 @@ mergeInto(LibraryManager.library, { // entries here that contain typed arrays. $wasmFS$JSMemoryFiles: {}, - _wasmfs_backend_add_jsfile__deps: [ + _wasmfs_backend_add_js_file__deps: [ '$wasmFS$backends', '$wasmFS$JSMemoryFiles', ], - _wasmfs_backend_add_jsfile: function(backend) { + _wasmfs_backend_add_js_file: function(backend) { wasmFS$backends[backend] = { constructor: function(file) {}, destructor: function(file) {}, diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index c27a6742ec8ed..5e2d9f899b058 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -1,4 +1,4 @@ -// Copyright 2021 The Emscripten Authors. All rights reserved. +// Copyright 2022 The Emscripten Authors. All rights reserved. // Emscripten is available under two separate licenses, the MIT license and the // University of Illinois/NCSA Open Source License. Both these licenses can be // found in the LICENSE file. @@ -8,82 +8,17 @@ // See https://github.com/emscripten-core/emscripten/issues/15041. #include "backend.h" +#include "js_impl_backend.h" #include "wasmfs.h" -// TODO rename this file -// TODO add docs for "how to maek a backend" - -using js_index_t = uint32_t; +// See library_wasmfs_js_file. extern "C" { -// JSImpl API (see below for overview). -int _wasmfs_jsimpl_write(js_index_t backend, - js_index_t index, - const uint8_t* buffer, - size_t length, - off_t offset); -int _wasmfs_jsimpl_read(js_index_t backend, - js_index_t index, - const uint8_t* buffer, - size_t length, - off_t offset); -int _wasmfs_jsimpl_get_size(js_index_t backend, js_index_t index); -void _wasmfs_jsimpl_constructor(js_index_t backend, js_index_t index); -void _wasmfs_jsimpl_destructor(js_index_t backend, js_index_t index); - -// Backends. -void _wasmfs_backend_add_jsfile(js_index_t); +void _wasmfs_backend_add_js_file(wasmfs::backend_t); } - namespace wasmfs { -// A "JSImplFile" is a file that is implemented by JS code. Which particular -// JS code handles it is indicated by a pointer to the backend. The JS code on -// the other side connects that pointer to the actual JS code, basically adding -// a layer of indirection that way. This allows a single C++ class here to have -// multiple JS implementations, which makes it easy to write new JS backends -// without C++ boilerplate. -// -// Each file operation in the _wasmfs_jsimpl_* APIs that we call from here take -// the backend and a pointer to this file itself. Those allow the JS to identify -// both the backend and the particular file. TODO: We could use dense indexes -// instead of pointers, which cause JS to use a map and not an array. -class JSImplFile : public DataFile { - js_index_t getBackendIndex() { - static_assert(sizeof(backend_t) == sizeof(js_index_t), "TODO: wasm64"); - return js_index_t(getBackend()); - } - - js_index_t getFileIndex() { - static_assert(sizeof(this) == sizeof(js_index_t), "TODO: wasm64"); - return js_index_t(this); - } - - __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { - return _wasmfs_jsimpl_write(getBackendIndex(), getFileIndex(), buf, len, offset); - } - - __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { - // The caller should have already checked that the offset + len does - // not exceed the file's size. - assert(offset + len <= getSize()); - return _wasmfs_jsimpl_read(getBackendIndex(), getFileIndex(), buf, len, offset); - } - - void flush() override {} - - size_t getSize() override { return _wasmfs_jsimpl_get_size(getBackendIndex(), getFileIndex()); } - -public: - JSImplFile(mode_t mode, backend_t backend) : DataFile(mode, backend) { - _wasmfs_jsimpl_constructor(getBackendIndex(), getFileIndex()); - } - - ~JSImplFile() { _wasmfs_jsimpl_destructor(getBackendIndex(), getFileIndex()); } -}; - -// This backend's files reside in JS Memory. class JSFileBackend : public Backend { public: std::shared_ptr createFile(mode_t mode) override { @@ -91,10 +26,9 @@ class JSFileBackend : public Backend { } }; -// This function is exposed to users to instantiate a new JSBackend. extern "C" backend_t wasmfs_create_js_file_backend() { backend_t backend = wasmFS.addBackend(std::make_unique()); - _wasmfs_backend_add_jsfile(js_index_t(backend)); // helper to convert with above? + _wasmfs_backend_add_js_file(backend); return backend;; } diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h new file mode 100644 index 0000000000000..3d9bd92c3fed7 --- /dev/null +++ b/system/lib/wasmfs/js_impl_backend.h @@ -0,0 +1,87 @@ +// Copyright 2021 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, the MIT license and the +// University of Illinois/NCSA Open Source License. Both these licenses can be +// found in the LICENSE file. + +// This file defines the JS file backend and JS file of the new file system. +// Current Status: Work in Progress. +// See https://github.com/emscripten-core/emscripten/issues/15041. + +#pragma once + +#include "backend.h" +#include "wasmfs.h" + +// A JS Impl backend has files that are implemented by JS code. Which particular +// JS code handles it is indicated by a pointer to the backend. The JS code on +// the other side connects that pointer to the actual JS code, basically adding +// a layer of indirection that way. This allows a single C++ class here to have +// multiple JS implementations, which makes it easy to write new JS backends +// without C++ boilerplate. +// +// Each file operation in the _wasmfs_jsimpl_* APIs that we call from here take +// the backend and a pointer to this file itself. Those allow the JS to identify +// both the backend and the particular file. TODO: We could use dense indexes +// instead of pointers, which cause JS to use a map and not an array. +// +// To write a new backend in JS, you basically do the following: + +// TODO add docs for "how to maek a backend" + +using js_index_t = uint32_t; + +extern "C" { +// JSImpl API (see below for overview). +int _wasmfs_jsimpl_write(js_index_t backend, + js_index_t index, + const uint8_t* buffer, + size_t length, + off_t offset); +int _wasmfs_jsimpl_read(js_index_t backend, + js_index_t index, + const uint8_t* buffer, + size_t length, + off_t offset); +int _wasmfs_jsimpl_get_size(js_index_t backend, js_index_t index); +void _wasmfs_jsimpl_constructor(js_index_t backend, js_index_t index); +void _wasmfs_jsimpl_destructor(js_index_t backend, js_index_t index); +} + + +namespace wasmfs { + +class JSImplFile : public DataFile { + js_index_t getBackendIndex() { + static_assert(sizeof(backend_t) == sizeof(js_index_t), "TODO: wasm64"); + return js_index_t(getBackend()); + } + + js_index_t getFileIndex() { + static_assert(sizeof(this) == sizeof(js_index_t), "TODO: wasm64"); + return js_index_t(this); + } + + __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { + return _wasmfs_jsimpl_write(getBackendIndex(), getFileIndex(), buf, len, offset); + } + + __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { + // The caller should have already checked that the offset + len does + // not exceed the file's size. + assert(offset + len <= getSize()); + return _wasmfs_jsimpl_read(getBackendIndex(), getFileIndex(), buf, len, offset); + } + + void flush() override {} + + size_t getSize() override { return _wasmfs_jsimpl_get_size(getBackendIndex(), getFileIndex()); } + +public: + JSImplFile(mode_t mode, backend_t backend) : DataFile(mode, backend) { + _wasmfs_jsimpl_constructor(getBackendIndex(), getFileIndex()); + } + + ~JSImplFile() { _wasmfs_jsimpl_destructor(getBackendIndex(), getFileIndex()); } +}; + +} // namespace wasmfs From 5a0653a804f6b4112fc49ffeed14e571419c0f21 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 15:01:58 -0800 Subject: [PATCH 08/61] comment --- system/include/emscripten/wasmfs.h | 14 ++++++++------ system/lib/wasmfs/js_impl_backend.h | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/system/include/emscripten/wasmfs.h b/system/include/emscripten/wasmfs.h index 1ffb0d0e80235..0ccd0a7f69d1c 100644 --- a/system/include/emscripten/wasmfs.h +++ b/system/include/emscripten/wasmfs.h @@ -22,18 +22,20 @@ backend_t wasmfs_get_backend_by_path(char* path); // Obtains the backend_t of a specified fd. backend_t wasmfs_get_backend_by_fd(int fd); -// Creates a JSFile Backend in the new file system. -backend_t wasmfs_create_js_file_backend(); - -// Creates a Proxied Backend in the new file system. -backend_t wasmfs_create_proxied_backend(backend_t backend); - // Creates a new file in the new file system under a specific backend. uint32_t wasmfs_create_file(char* pathname, mode_t mode, backend_t backend); // Creates a new directory in the new file system under a specific backend. long wasmfs_create_directory(char* path, long mode, backend_t backend); +// Backend creation + +// Creates a JSFile Backend in the new file system. +backend_t wasmfs_create_js_file_backend(); + +// Creates a Proxied Backend in the new file system. +backend_t wasmfs_create_proxied_backend(backend_t backend); + #ifdef __cplusplus } #endif diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h index 3d9bd92c3fed7..3821704d6f81f 100644 --- a/system/lib/wasmfs/js_impl_backend.h +++ b/system/lib/wasmfs/js_impl_backend.h @@ -12,6 +12,7 @@ #include "backend.h" #include "wasmfs.h" +// // A JS Impl backend has files that are implemented by JS code. Which particular // JS code handles it is indicated by a pointer to the backend. The JS code on // the other side connects that pointer to the actual JS code, basically adding @@ -25,8 +26,19 @@ // instead of pointers, which cause JS to use a map and not an array. // // To write a new backend in JS, you basically do the following: - -// TODO add docs for "how to maek a backend" +// +// 1. Add a declaration of the C function to create the backend in the +// "backend creation" section of emscripten/wasmfs.h. +// 2. Add a cpp file with the backend, that creates JSImplFile instances for +// its files. +// 3. In the implementation of the C function from 1, create a backend and also +// call a JS method to set up the JS side. +// 4. Write a new JS library starting with the implementation of the JS method +// just mentioned, which should set wasmFS$backends[backend] with a JS +// object containing the hooks to read and write and so forth. +// +// For a simple example, see js_file_backend.cpp and library_wasmfs_js_file.js +// using js_index_t = uint32_t; From 17c041fd80f71dcd927e199a34e5b6cfef446ac5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 15:10:00 -0800 Subject: [PATCH 09/61] better --- src/library_wasmfs_js_file.js | 9 +++++-- system/lib/wasmfs/js_file_backend.cpp | 11 ++------ system/lib/wasmfs/js_impl_backend.h | 36 ++++++++++++++++----------- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/library_wasmfs_js_file.js b/src/library_wasmfs_js_file.js index 9aae77c13eb8b..4d869859d3ed5 100644 --- a/src/library_wasmfs_js_file.js +++ b/src/library_wasmfs_js_file.js @@ -9,8 +9,13 @@ mergeInto(LibraryManager.library, { ], _wasmfs_backend_add_js_file: function(backend) { wasmFS$backends[backend] = { - constructor: function(file) {}, - destructor: function(file) {}, + constructor: function(file) { + // Do nothing: we allocate the typed array lazily, see write() + }, + destructor: function(file) { + // Release the memory, as it now has no references to it any more. + wasmFS$JSMemoryFiles[file] = undefined; + }, write: function(file, buffer, length, offset) { try { if (!wasmFS$JSMemoryFiles[file]) { diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index 5e2d9f899b058..95911ce060976 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 The Emscripten Authors. All rights reserved. +// Copyright 2021 The Emscripten Authors. All rights reserved. // Emscripten is available under two separate licenses, the MIT license and the // University of Illinois/NCSA Open Source License. Both these licenses can be // found in the LICENSE file. @@ -19,15 +19,8 @@ void _wasmfs_backend_add_js_file(wasmfs::backend_t); namespace wasmfs { -class JSFileBackend : public Backend { -public: - std::shared_ptr createFile(mode_t mode) override { - return std::make_shared(mode, this); - } -}; - extern "C" backend_t wasmfs_create_js_file_backend() { - backend_t backend = wasmFS.addBackend(std::make_unique()); + backend_t backend = wasmFS.addBackend(std::make_unique()); _wasmfs_backend_add_js_file(backend); return backend;; } diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h index 3821704d6f81f..fe4159c89c595 100644 --- a/system/lib/wasmfs/js_impl_backend.h +++ b/system/lib/wasmfs/js_impl_backend.h @@ -1,4 +1,4 @@ -// Copyright 2021 The Emscripten Authors. All rights reserved. +// Copyright 2022 The Emscripten Authors. All rights reserved. // Emscripten is available under two separate licenses, the MIT license and the // University of Illinois/NCSA Open Source License. Both these licenses can be // found in the LICENSE file. @@ -13,29 +13,28 @@ #include "wasmfs.h" // -// A JS Impl backend has files that are implemented by JS code. Which particular -// JS code handles it is indicated by a pointer to the backend. The JS code on -// the other side connects that pointer to the actual JS code, basically adding -// a layer of indirection that way. This allows a single C++ class here to have -// multiple JS implementations, which makes it easy to write new JS backends -// without C++ boilerplate. +// A JS Impl backend has files that are implemented by JS code. Each backend +// object in C++ can call JS to define the proper JS code on that side, and we +// keep a mapping of C++ pointer of backend => JS code in JS. Then, when we do +// something like a read from a JSImplFile we pass it the backend, find the +// proper code to run, and run it. This adds a layer of indirection at the JS +// level that makes it easy to write new backends in 99% JS. // // Each file operation in the _wasmfs_jsimpl_* APIs that we call from here take // the backend and a pointer to this file itself. Those allow the JS to identify // both the backend and the particular file. TODO: We could use dense indexes -// instead of pointers, which cause JS to use a map and not an array. +// instead of pointers, and use an array instead of a map. // // To write a new backend in JS, you basically do the following: // // 1. Add a declaration of the C function to create the backend in the // "backend creation" section of emscripten/wasmfs.h. -// 2. Add a cpp file with the backend, that creates JSImplFile instances for -// its files. -// 3. In the implementation of the C function from 1, create a backend and also -// call a JS method to set up the JS side. -// 4. Write a new JS library starting with the implementation of the JS method -// just mentioned, which should set wasmFS$backends[backend] with a JS -// object containing the hooks to read and write and so forth. +// 2. Add a cpp file for the new backend, and implement the C function from 1, +// which should create it on both the C++ (using JSImplBackend) and JS +// sides. +// 3. Write a new JS library, and add the implementation of the JS method just +// mentioned, which should set up the mapping from the C++ backend object's +// address to the JS code containing the hooks to read and write etc. // // For a simple example, see js_file_backend.cpp and library_wasmfs_js_file.js // @@ -96,4 +95,11 @@ class JSImplFile : public DataFile { ~JSImplFile() { _wasmfs_jsimpl_destructor(getBackendIndex(), getFileIndex()); } }; +class JSFileBackend : public Backend { +public: + std::shared_ptr createFile(mode_t mode) override { + return std::make_shared(mode, this); + } +}; + } // namespace wasmfs From cb4c42f6606be4fe11571646f0ec9362be75fd5e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 15:10:26 -0800 Subject: [PATCH 10/61] format --- system/lib/wasmfs/js_file_backend.cpp | 3 ++- system/lib/wasmfs/js_impl_backend.h | 15 ++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index 95911ce060976..44e139f88886e 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -22,7 +22,8 @@ namespace wasmfs { extern "C" backend_t wasmfs_create_js_file_backend() { backend_t backend = wasmFS.addBackend(std::make_unique()); _wasmfs_backend_add_js_file(backend); - return backend;; + return backend; + ; } } // namespace wasmfs diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h index fe4159c89c595..41f21ba840231 100644 --- a/system/lib/wasmfs/js_impl_backend.h +++ b/system/lib/wasmfs/js_impl_backend.h @@ -58,7 +58,6 @@ void _wasmfs_jsimpl_constructor(js_index_t backend, js_index_t index); void _wasmfs_jsimpl_destructor(js_index_t backend, js_index_t index); } - namespace wasmfs { class JSImplFile : public DataFile { @@ -73,26 +72,32 @@ class JSImplFile : public DataFile { } __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { - return _wasmfs_jsimpl_write(getBackendIndex(), getFileIndex(), buf, len, offset); + return _wasmfs_jsimpl_write( + getBackendIndex(), getFileIndex(), buf, len, offset); } __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { // The caller should have already checked that the offset + len does // not exceed the file's size. assert(offset + len <= getSize()); - return _wasmfs_jsimpl_read(getBackendIndex(), getFileIndex(), buf, len, offset); + return _wasmfs_jsimpl_read( + getBackendIndex(), getFileIndex(), buf, len, offset); } void flush() override {} - size_t getSize() override { return _wasmfs_jsimpl_get_size(getBackendIndex(), getFileIndex()); } + size_t getSize() override { + return _wasmfs_jsimpl_get_size(getBackendIndex(), getFileIndex()); + } public: JSImplFile(mode_t mode, backend_t backend) : DataFile(mode, backend) { _wasmfs_jsimpl_constructor(getBackendIndex(), getFileIndex()); } - ~JSImplFile() { _wasmfs_jsimpl_destructor(getBackendIndex(), getFileIndex()); } + ~JSImplFile() { + _wasmfs_jsimpl_destructor(getBackendIndex(), getFileIndex()); + } }; class JSFileBackend : public Backend { From 5fa4f25d9f72fbbcda050007ef193df48cfd42a3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 15:11:06 -0800 Subject: [PATCH 11/61] fix --- system/lib/wasmfs/js_impl_backend.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h index 41f21ba840231..0ca2d969c5161 100644 --- a/system/lib/wasmfs/js_impl_backend.h +++ b/system/lib/wasmfs/js_impl_backend.h @@ -100,7 +100,7 @@ class JSImplFile : public DataFile { } }; -class JSFileBackend : public Backend { +class JSImplBackend : public Backend { public: std::shared_ptr createFile(mode_t mode) override { return std::make_shared(mode, this); From 650efabea3436155f520b775a8c88f1fcd1d6037 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Feb 2022 15:12:00 -0800 Subject: [PATCH 12/61] comments --- system/lib/wasmfs/js_file_backend.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index 44e139f88886e..4a37270ed43ee 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -4,14 +4,13 @@ // found in the LICENSE file. // This file defines the JS file backend and JS file of the new file system. -// Current Status: Work in Progress. // See https://github.com/emscripten-core/emscripten/issues/15041. #include "backend.h" #include "js_impl_backend.h" #include "wasmfs.h" -// See library_wasmfs_js_file. +// See library_wasmfs_js_file.js extern "C" { void _wasmfs_backend_add_js_file(wasmfs::backend_t); @@ -23,7 +22,6 @@ extern "C" backend_t wasmfs_create_js_file_backend() { backend_t backend = wasmFS.addBackend(std::make_unique()); _wasmfs_backend_add_js_file(backend); return backend; - ; } } // namespace wasmfs From c80a891806cda39ade00bc8d0ea581dddd611668 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 7 Feb 2022 13:04:03 -0800 Subject: [PATCH 13/61] comment [ci skip] --- system/lib/wasmfs/js_impl_backend.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h index 0ca2d969c5161..d6b339bf89adc 100644 --- a/system/lib/wasmfs/js_impl_backend.h +++ b/system/lib/wasmfs/js_impl_backend.h @@ -31,7 +31,8 @@ // "backend creation" section of emscripten/wasmfs.h. // 2. Add a cpp file for the new backend, and implement the C function from 1, // which should create it on both the C++ (using JSImplBackend) and JS -// sides. +// sides. (By convention, the C function should just call into C++ and JS +// which do the interesting work; the C is just a thin wrapper.) // 3. Write a new JS library, and add the implementation of the JS method just // mentioned, which should set up the mapping from the C++ backend object's // address to the JS code containing the hooks to read and write etc. From e3c9012ce340db4598578fc8d0817cf8b7c08df6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 08:24:22 -0800 Subject: [PATCH 14/61] rename --- src/library_wasmfs.js | 8 ++++---- src/library_wasmfs_js_file.js | 4 ++-- system/lib/wasmfs/js_impl_backend.h | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 67be73fd19efb..cfbf94dd48b58 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -153,18 +153,18 @@ var WasmFSLibrary = { // in C++, together with the js_impl calls defined right after it. $wasmFS$backends: {}, - _wasmfs_jsimpl_constructor: function(backend, file) { + _wasmfs_jsimpl_alloc_file: function(backend, file) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - return wasmFS$backends[backend].constructor(file); + return wasmFS$backends[backend].alloc_file(file); }, - _wasmfs_jsimpl_destructor: function(backend, file) { + _wasmfs_jsimpl_free_file: function(backend, file) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - return wasmFS$backends[backend].destructor(file); + return wasmFS$backends[backend].free_file(file); }, _wasmfs_jsimpl_write: function(backend, file, buffer, length, offset) { diff --git a/src/library_wasmfs_js_file.js b/src/library_wasmfs_js_file.js index 4d869859d3ed5..d0e8535ce20c3 100644 --- a/src/library_wasmfs_js_file.js +++ b/src/library_wasmfs_js_file.js @@ -9,10 +9,10 @@ mergeInto(LibraryManager.library, { ], _wasmfs_backend_add_js_file: function(backend) { wasmFS$backends[backend] = { - constructor: function(file) { + alloc_file: function(file) { // Do nothing: we allocate the typed array lazily, see write() }, - destructor: function(file) { + free_file: function(file) { // Release the memory, as it now has no references to it any more. wasmFS$JSMemoryFiles[file] = undefined; }, diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h index d6b339bf89adc..4735c9acc1976 100644 --- a/system/lib/wasmfs/js_impl_backend.h +++ b/system/lib/wasmfs/js_impl_backend.h @@ -44,6 +44,8 @@ using js_index_t = uint32_t; extern "C" { // JSImpl API (see below for overview). +void _wasmfs_jsimpl_alloc_file(js_index_t backend, js_index_t index); +void _wasmfs_jsimpl_free_file(js_index_t backend, js_index_t index); int _wasmfs_jsimpl_write(js_index_t backend, js_index_t index, const uint8_t* buffer, @@ -55,8 +57,6 @@ int _wasmfs_jsimpl_read(js_index_t backend, size_t length, off_t offset); int _wasmfs_jsimpl_get_size(js_index_t backend, js_index_t index); -void _wasmfs_jsimpl_constructor(js_index_t backend, js_index_t index); -void _wasmfs_jsimpl_destructor(js_index_t backend, js_index_t index); } namespace wasmfs { @@ -93,11 +93,11 @@ class JSImplFile : public DataFile { public: JSImplFile(mode_t mode, backend_t backend) : DataFile(mode, backend) { - _wasmfs_jsimpl_constructor(getBackendIndex(), getFileIndex()); + _wasmfs_jsimpl_alloc_file(getBackendIndex(), getFileIndex()); } ~JSImplFile() { - _wasmfs_jsimpl_destructor(getBackendIndex(), getFileIndex()); + _wasmfs_jsimpl_free_file(getBackendIndex(), getFileIndex()); } }; From 0e5429ba1b2db087ef28f63f44cc3ecfcac56e6e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 10:34:17 -0800 Subject: [PATCH 15/61] start [ci skip] --- .../lib/wasmfs/proxed_async_js_impl_backend.h | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 system/lib/wasmfs/proxed_async_js_impl_backend.h diff --git a/system/lib/wasmfs/proxed_async_js_impl_backend.h b/system/lib/wasmfs/proxed_async_js_impl_backend.h new file mode 100644 index 0000000000000..ed817b2aada56 --- /dev/null +++ b/system/lib/wasmfs/proxed_async_js_impl_backend.h @@ -0,0 +1,94 @@ +// Copyright 2022 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, the MIT license and the +// University of Illinois/NCSA Open Source License. Both these licenses can be +// found in the LICENSE file. + +// This file defines the JS file backend and JS file of the new file system. +// Current Status: Work in Progress. +// See https://github.com/emscripten-core/emscripten/issues/15041. + +#pragma once + +#include "backend.h" +#include "thread_utils.h" +#include "wasmfs.h" + +// +// Similar to JSImplBackend, but proxies to another thread where the JS can be +// async. +// + +using js_index_t = uint32_t; + +extern "C" { +// JSImpl async API. +void _wasmfs_jsimpl_async_alloc_file(js_index_t backend, js_index_t index); +void _wasmfs_jsimpl_async_free_file(js_index_t backend, js_index_t index); +int _wasmfs_jsimpl_async_write(js_index_t backend, + js_index_t index, + const uint8_t* buffer, + size_t length, + off_t offset); +int _wasmfs_jsimpl_async_read(js_index_t backend, + js_index_t index, + const uint8_t* buffer, + size_t length, + off_t offset); +int _wasmfs_jsimpl_async_get_size(js_index_t backend, js_index_t index); +} + +namespace wasmfs { + +class ProxiedAsyncJSImplFile : public DataFile { + emscripten::SyncToAsync& proxy; + + js_index_t getBackendIndex() { + static_assert(sizeof(backend_t) == sizeof(js_index_t), "TODO: wasm64"); + return js_index_t(getBackend()); + } + + js_index_t getFileIndex() { + static_assert(sizeof(this) == sizeof(js_index_t), "TODO: wasm64"); + return js_index_t(this); + } + + __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { + return _wasmfs_jsimpl_async_write( + getBackendIndex(), getFileIndex(), buf, len, offset); + } + + __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { + // The caller should have already checked that the offset + len does + // not exceed the file's size. + assert(offset + len <= getSize()); + return _wasmfs_jsimpl_async_read( + getBackendIndex(), getFileIndex(), buf, len, offset); + } + + void flush() override {} + + size_t getSize() override { + return _wasmfs_jsimpl_async_get_size(getBackendIndex(), getFileIndex()); + } + +public: + ProxiedAsyncJSImplFile(mode_t mode, backend_t backend, emscripten::SyncToAsync& proxy) : DataFile(mode, backend), proxy(proxy) { + proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { + _wasmfs_jsimpl_async_alloc_file(getBackendIndex(), getFileIndex()); + (*resume)(); + }); + } + + ~ProxiedAsyncJSImplFile() { + _wasmfs_jsimpl_async_free_file(getBackendIndex(), getFileIndex()); + } +}; + +class ProxiedAsyncJSImplBackend : public Backend { +public: + std::shared_ptr createFile(mode_t mode) override { + return std::make_shared(mode, this); + } +}; + +} // namespace wasmfs From dc20c8dcd84241098272061be7d7fa2e52e41ee7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 10:46:19 -0800 Subject: [PATCH 16/61] wip [ci skip] --- system/include/emscripten/wasmfs.h | 2 ++ system/lib/wasmfs/fetch_backend.cpp | 28 +++++++++++++++++++ .../lib/wasmfs/proxed_async_js_impl_backend.h | 22 ++++++++++----- 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 system/lib/wasmfs/fetch_backend.cpp diff --git a/system/include/emscripten/wasmfs.h b/system/include/emscripten/wasmfs.h index b990a9ec37f69..7b86bb72fec8f 100644 --- a/system/include/emscripten/wasmfs.h +++ b/system/include/emscripten/wasmfs.h @@ -40,6 +40,8 @@ typedef backend_t (*backend_constructor_t)(void*); backend_t wasmfs_create_proxied_backend(backend_constructor_t create_backend, void* arg); +backend_t wasmfs_create_fetch_backend(char* base_url); + #ifdef __cplusplus } #endif diff --git a/system/lib/wasmfs/fetch_backend.cpp b/system/lib/wasmfs/fetch_backend.cpp new file mode 100644 index 0000000000000..995882e049798 --- /dev/null +++ b/system/lib/wasmfs/fetch_backend.cpp @@ -0,0 +1,28 @@ +// Copyright 2021 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, the MIT license and the +// University of Illinois/NCSA Open Source License. Both these licenses can be +// found in the LICENSE file. + +// This file defines the JS file backend and JS file of the new file system. +// See https://github.com/emscripten-core/emscripten/issues/15041. + +#include "backend.h" +#include "proxied_async_js_impl_backend.h" +#include "wasmfs.h" + +// See library_wasmfs_fetch.js + +extern "C" { +void _wasmfs_backend_add_fetch(wasmfs::backend_t); +} + +namespace wasmfs { + +extern "C" backend_t wasmfs_create_fetch_backend(char* base_url) { + // TODO: use base url, cache on JS side + backend_t backend = wasmFS.addBackend(std::make_unique()); + _wasmfs_backend_add_fetch(backend); + return backend; +} + +} // namespace wasmfs diff --git a/system/lib/wasmfs/proxed_async_js_impl_backend.h b/system/lib/wasmfs/proxed_async_js_impl_backend.h index ed817b2aada56..7564e979645f8 100644 --- a/system/lib/wasmfs/proxed_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxed_async_js_impl_backend.h @@ -22,19 +22,21 @@ using js_index_t = uint32_t; extern "C" { // JSImpl async API. -void _wasmfs_jsimpl_async_alloc_file(js_index_t backend, js_index_t index); -void _wasmfs_jsimpl_async_free_file(js_index_t backend, js_index_t index); +typedef void (*async_callback_t)(void*); + +void _wasmfs_jsimpl_async_alloc_file(js_index_t backend, js_index_t index, async_callback_t callback, void* arg); +void _wasmfs_jsimpl_async_free_file(js_index_t backend, js_index_t index, async_callback_t callback, void* arg); int _wasmfs_jsimpl_async_write(js_index_t backend, js_index_t index, const uint8_t* buffer, size_t length, - off_t offset); + off_t offset, async_callback_t callback, void* arg); int _wasmfs_jsimpl_async_read(js_index_t backend, js_index_t index, const uint8_t* buffer, size_t length, - off_t offset); -int _wasmfs_jsimpl_async_get_size(js_index_t backend, js_index_t index); + off_t offset, async_callback_t callback, void* arg); +int _wasmfs_jsimpl_async_get_size(js_index_t backend, js_index_t index, async_callback_t callback, void* arg); } namespace wasmfs { @@ -53,11 +55,13 @@ class ProxiedAsyncJSImplFile : public DataFile { } __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { + // TODO; proxy return _wasmfs_jsimpl_async_write( getBackendIndex(), getFileIndex(), buf, len, offset); } __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { + // TODO; proxy // The caller should have already checked that the offset + len does // not exceed the file's size. assert(offset + len <= getSize()); @@ -68,18 +72,22 @@ class ProxiedAsyncJSImplFile : public DataFile { void flush() override {} size_t getSize() override { + // TODO; proxy return _wasmfs_jsimpl_async_get_size(getBackendIndex(), getFileIndex()); } public: ProxiedAsyncJSImplFile(mode_t mode, backend_t backend, emscripten::SyncToAsync& proxy) : DataFile(mode, backend), proxy(proxy) { proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { - _wasmfs_jsimpl_async_alloc_file(getBackendIndex(), getFileIndex()); - (*resume)(); + _wasmfs_jsimpl_async_alloc_file(getBackendIndex(), getFileIndex(), [](void* arg) { + auto* resume = (emscripten::SyncToAsync::Callback*)arg; + (**resume)(); + }, (void*)&resume); }); } ~ProxiedAsyncJSImplFile() { + // TODO; proxy _wasmfs_jsimpl_async_free_file(getBackendIndex(), getFileIndex()); } }; From 6210afad608223a523d39c5292c6819e0c78b788 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 11:04:14 -0800 Subject: [PATCH 17/61] [ci skip] --- src/library_wasmfs.js | 46 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index cfbf94dd48b58..e2551cbd3e065 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -149,10 +149,12 @@ var WasmFSLibrary = { }, // Backend support. wasmFS$backends will contain a mapping of backend IDs to - // the JS code that implements them. This is the JS side of the JSImpl class + // the JS code that implements them. This is the JS side of the JSImpl* class // in C++, together with the js_impl calls defined right after it. $wasmFS$backends: {}, + // JSImpl + _wasmfs_jsimpl_alloc_file: function(backend, file) { #if ASSERTIONS assert(wasmFS$backends[backend]); @@ -184,6 +186,48 @@ var WasmFSLibrary = { _wasmfs_jsimpl_get_size: function(backend, file) { #if ASSERTIONS assert(wasmFS$backends[backend]); +#endif + return wasmFS$backends[backend].getSize(file); + }, + + // ProxiedAsyncJSImpl. Each function receives a function pointer and a + // parameter. We convert those into a convenient Promise API for the + // implementors of backends: the hooks we call should return Promises, which + // we then connect to the calling C++. + + _wasmfs_async_jsimpl_alloc_file: function(backend, file, fptr, arg) { +#if ASSERTIONS + assert(wasmFS$backends[backend]); +#endif + wasmFS$backends[backend].alloc_file(file).then((result) => { + {{{ makeDynCall('vii', 'fptr') }}}(arg, result); + }); + }, + + _wasmfs_async_jsimpl_free_file: function(backend, file) { +#if ASSERTIONS + assert(wasmFS$backends[backend]); +#endif + return wasmFS$backends[backend].free_file(file); + }, + + _wasmfs_async_jsimpl_write: function(backend, file, buffer, length, offset) { +#if ASSERTIONS + assert(wasmFS$backends[backend]); +#endif + return wasmFS$backends[backend].write(file, buffer, length, offset); + }, + + _wasmfs_async_jsimpl_read: function(backend, file, buffer, length, offset) { +#if ASSERTIONS + assert(wasmFS$backends[backend]); +#endif + return wasmFS$backends[backend].read(file, buffer, length, offset); + }, + + _wasmfs_async_jsimpl_get_size: function(backend, file) { +#if ASSERTIONS + assert(wasmFS$backends[backend]); #endif return wasmFS$backends[backend].getSize(file); }, From af004a0b495bb66bc26b3b8c16e985abd092b07b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 11:39:28 -0800 Subject: [PATCH 18/61] work [ci skip] --- src/library_wasmfs.js | 4 +-- src/library_wasmfs_fetch.js | 57 +++++++++++++++++++++++++++++++++++++ src/modules.js | 1 + 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/library_wasmfs_fetch.js diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index e2551cbd3e065..6ca01770e123f 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -199,8 +199,8 @@ var WasmFSLibrary = { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - wasmFS$backends[backend].alloc_file(file).then((result) => { - {{{ makeDynCall('vii', 'fptr') }}}(arg, result); + wasmFS$backends[backend].alloc_file(file).then(() => { + {{{ makeDynCall('vii', 'fptr') }}}(arg); }); }, diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js new file mode 100644 index 0000000000000..ba151f0b23551 --- /dev/null +++ b/src/library_wasmfs_fetch.js @@ -0,0 +1,57 @@ +mergeInto(LibraryManager.library, { + // Fetch backend: On first read, does a fetch() to get the data. It then + // behaves like JSFile + + _wasmfs_backend_add_fetch__deps: [ + '$wasmFS$backends', + '$wasmFS$JSMemoryFiles', + '_wasmfs_backend_add_js_file', + ], + _wasmfs_backend_add_fetch: function(backend) { + // Start with the normal JSFile operations. + __wasmfs_backend_add_js_file(backend); + + // Add the async operations on top. + var jsFileOps = wasmFS$backends[backend]; + wasmFS$backends[backend] = { + alloc_file: function(file) { + jsFileOps.alloc_file(file); + return new Promise.resolve(); + }, + free_file: function(file) { + jsFileOps.free_file(file); + return new Promise.resolve(); + }, + write: function(file, buffer, length, offset) { + abort(); + }, + read: function(file, buffer, length, offset) { + var promise; + if (!wasmFS$JSMemoryFiles[file]) { + // This is the first read from this file, fetch it. + // TODO: URL! + promise = fetch('data.dat').then((response) => + if (!response['ok']) { + throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; + } + return response['arrayBuffer'](); + }).then((buffer) => { + wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); + }); + } else { + // The data is already here, so nothing to do before we continue on to + // the actual read below. + promise = Promise.resolve(); + } + + // Do the actual read. + return promise.then(() => { + return jsFileOps.read(file, buffer, length, offset); + }); + }, + getSize: function(file) { + return new Promise.resolve(jsFileOps.getSize(file)); + }, + }; + }, +}); diff --git a/src/modules.js b/src/modules.js index 6be6604548909..f6440b59280f4 100644 --- a/src/modules.js +++ b/src/modules.js @@ -96,6 +96,7 @@ global.LibraryManager = { } else if (WASMFS) { libraries.push('library_wasmfs.js'); libraries.push('library_wasmfs_js_file.js'); + libraries.push('library_wasmfs_fetch.js'); } // Additional JS libraries (without AUTO_JS_LIBRARIES, link to these explicitly via -lxxx.js) From 19c0c9e661bd1d7ae4d8281949b74e01f6e6e055 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 11:46:58 -0800 Subject: [PATCH 19/61] cpp builds [ci skip] --- ...mpl_backend.h => proxied_async_js_impl_backend.h} | 12 +++++++----- system/lib/wasmfs/thread_utils.h | 2 +- tools/system_libs.py | 1 + 3 files changed, 9 insertions(+), 6 deletions(-) rename system/lib/wasmfs/{proxed_async_js_impl_backend.h => proxied_async_js_impl_backend.h} (91%) diff --git a/system/lib/wasmfs/proxed_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h similarity index 91% rename from system/lib/wasmfs/proxed_async_js_impl_backend.h rename to system/lib/wasmfs/proxied_async_js_impl_backend.h index 7564e979645f8..2b1cb5b1443e3 100644 --- a/system/lib/wasmfs/proxed_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -57,7 +57,7 @@ class ProxiedAsyncJSImplFile : public DataFile { __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { // TODO; proxy return _wasmfs_jsimpl_async_write( - getBackendIndex(), getFileIndex(), buf, len, offset); + getBackendIndex(), getFileIndex(), buf, len, offset, nullptr, nullptr); } __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { @@ -66,14 +66,14 @@ class ProxiedAsyncJSImplFile : public DataFile { // not exceed the file's size. assert(offset + len <= getSize()); return _wasmfs_jsimpl_async_read( - getBackendIndex(), getFileIndex(), buf, len, offset); + getBackendIndex(), getFileIndex(), buf, len, offset, nullptr, nullptr); } void flush() override {} size_t getSize() override { // TODO; proxy - return _wasmfs_jsimpl_async_get_size(getBackendIndex(), getFileIndex()); + return _wasmfs_jsimpl_async_get_size(getBackendIndex(), getFileIndex(), nullptr, nullptr); } public: @@ -88,14 +88,16 @@ class ProxiedAsyncJSImplFile : public DataFile { ~ProxiedAsyncJSImplFile() { // TODO; proxy - _wasmfs_jsimpl_async_free_file(getBackendIndex(), getFileIndex()); + _wasmfs_jsimpl_async_free_file(getBackendIndex(), getFileIndex(), nullptr, nullptr); } }; class ProxiedAsyncJSImplBackend : public Backend { + emscripten::SyncToAsync proxy; + public: std::shared_ptr createFile(mode_t mode) override { - return std::make_shared(mode, this); + return std::make_shared(mode, this, proxy); } }; diff --git a/system/lib/wasmfs/thread_utils.h b/system/lib/wasmfs/thread_utils.h index 6a4da16ed0430..e99b075f276a4 100644 --- a/system/lib/wasmfs/thread_utils.h +++ b/system/lib/wasmfs/thread_utils.h @@ -177,7 +177,7 @@ class SyncToAsync { } }; -void SyncToAsync::invoke(std::function newWork) { +inline void SyncToAsync::invoke(std::function newWork) { // The worker might not be waiting for work if some other invoker has already // sent work. Wait for the worker to be done with that work and ready for new // work. diff --git a/tools/system_libs.py b/tools/system_libs.py index a3eca215ca8cd..bf02903cd9c29 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1424,6 +1424,7 @@ def get_files(self): 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', + 'fetch_backend.cpp', 'js_api.cpp']) def can_use(self): From 9a4d052ab1dbdce2d3b934fb7339ef0717b15d07 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 12:43:48 -0800 Subject: [PATCH 20/61] rename --- src/library_wasmfs_js_file.js | 4 ++-- system/lib/wasmfs/js_file_backend.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/library_wasmfs_js_file.js b/src/library_wasmfs_js_file.js index d0e8535ce20c3..5652376ac4619 100644 --- a/src/library_wasmfs_js_file.js +++ b/src/library_wasmfs_js_file.js @@ -3,11 +3,11 @@ mergeInto(LibraryManager.library, { // entries here that contain typed arrays. $wasmFS$JSMemoryFiles: {}, - _wasmfs_backend_add_js_file__deps: [ + _wasmfs_create_js_file_backend_js__deps: [ '$wasmFS$backends', '$wasmFS$JSMemoryFiles', ], - _wasmfs_backend_add_js_file: function(backend) { + _wasmfs_create_js_file_backend_js: function(backend) { wasmFS$backends[backend] = { alloc_file: function(file) { // Do nothing: we allocate the typed array lazily, see write() diff --git a/system/lib/wasmfs/js_file_backend.cpp b/system/lib/wasmfs/js_file_backend.cpp index 4a37270ed43ee..41b484f51e5cb 100644 --- a/system/lib/wasmfs/js_file_backend.cpp +++ b/system/lib/wasmfs/js_file_backend.cpp @@ -13,14 +13,14 @@ // See library_wasmfs_js_file.js extern "C" { -void _wasmfs_backend_add_js_file(wasmfs::backend_t); +void _wasmfs_create_js_file_backend_js(wasmfs::backend_t); } namespace wasmfs { extern "C" backend_t wasmfs_create_js_file_backend() { backend_t backend = wasmFS.addBackend(std::make_unique()); - _wasmfs_backend_add_js_file(backend); + _wasmfs_create_js_file_backend_js(backend); return backend; } From 4718c65bdd2182c1f62a513e3c838ba2b929625e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 12:45:42 -0800 Subject: [PATCH 21/61] js 'compiles' [ci skip] --- src/library_wasmfs_fetch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index ba151f0b23551..56eb88e0368cf 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -30,7 +30,7 @@ mergeInto(LibraryManager.library, { if (!wasmFS$JSMemoryFiles[file]) { // This is the first read from this file, fetch it. // TODO: URL! - promise = fetch('data.dat').then((response) => + promise = fetch('data.dat').then((response) => { if (!response['ok']) { throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; } From 8b28d2035575cd42c42a871dd3831e0a8c1c0b68 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 12:48:38 -0800 Subject: [PATCH 22/61] node? [ci skip] --- src/library_wasmfs_fetch.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index 56eb88e0368cf..6b912a6c748d6 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -30,12 +30,20 @@ mergeInto(LibraryManager.library, { if (!wasmFS$JSMemoryFiles[file]) { // This is the first read from this file, fetch it. // TODO: URL! - promise = fetch('data.dat').then((response) => { - if (!response['ok']) { - throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; - } - return response['arrayBuffer'](); - }).then((buffer) => { + var url = 'data.dat'; + if (typeof fetch === 'function') { + // On the web use fetch() normally. + promise = fetch().then((response) => { + if (!response['ok']) { + throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; + } + return response['arrayBuffer'](); + }); + } else { + // Until Node.js gets fetch support, use an async read. + promise = fs.readFile(url, "binary"); + } + promise.then((buffer) => { wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); }); } else { From 3ec5f1f23ca09427cfeb361b48ee55c58e9435b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 13:19:53 -0800 Subject: [PATCH 23/61] work [ci skip] --- src/library_wasmfs.js | 18 +++++++++--------- src/library_wasmfs_fetch.js | 17 +++++++++-------- src/library_wasmfs_js_file.js | 4 ++-- system/lib/wasmfs/fetch_backend.cpp | 4 ++-- tests/test_other.py | 9 +++++++++ tests/wasmfs/wasmfs_jsfile.c | 17 +++++++++++------ 6 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 6ca01770e123f..0778ece46a083 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -159,14 +159,14 @@ var WasmFSLibrary = { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - return wasmFS$backends[backend].alloc_file(file); + return wasmFS$backends[backend].allocFile(file); }, _wasmfs_jsimpl_free_file: function(backend, file) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - return wasmFS$backends[backend].free_file(file); + return wasmFS$backends[backend].freeFile(file); }, _wasmfs_jsimpl_write: function(backend, file, buffer, length, offset) { @@ -195,37 +195,37 @@ var WasmFSLibrary = { // implementors of backends: the hooks we call should return Promises, which // we then connect to the calling C++. - _wasmfs_async_jsimpl_alloc_file: function(backend, file, fptr, arg) { + _wasmfs_jsimpl_async_alloc_file: function(backend, file, fptr, arg) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - wasmFS$backends[backend].alloc_file(file).then(() => { + wasmFS$backends[backend].allocFile(file).then(() => { {{{ makeDynCall('vii', 'fptr') }}}(arg); }); }, - _wasmfs_async_jsimpl_free_file: function(backend, file) { + _wasmfs_jsimpl_async_free_file: function(backend, file) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - return wasmFS$backends[backend].free_file(file); + return wasmFS$backends[backend].freeFile(file); }, - _wasmfs_async_jsimpl_write: function(backend, file, buffer, length, offset) { + _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, offset) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif return wasmFS$backends[backend].write(file, buffer, length, offset); }, - _wasmfs_async_jsimpl_read: function(backend, file, buffer, length, offset) { + _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offset) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif return wasmFS$backends[backend].read(file, buffer, length, offset); }, - _wasmfs_async_jsimpl_get_size: function(backend, file) { + _wasmfs_jsimpl_async_get_size: function(backend, file) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index 6b912a6c748d6..2ed8c326f5b43 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -2,24 +2,25 @@ mergeInto(LibraryManager.library, { // Fetch backend: On first read, does a fetch() to get the data. It then // behaves like JSFile - _wasmfs_backend_add_fetch__deps: [ + _wasmfs_create_fetch_backend_js__deps: [ '$wasmFS$backends', '$wasmFS$JSMemoryFiles', - '_wasmfs_backend_add_js_file', + '_wasmfs_create_js_file_backend_js', ], - _wasmfs_backend_add_fetch: function(backend) { + _wasmfs_create_fetch_backend_js: function(backend) { +console.log('maek fetch', backend); // Start with the normal JSFile operations. - __wasmfs_backend_add_js_file(backend); + __wasmfs_create_js_file_backend_js(backend); // Add the async operations on top. var jsFileOps = wasmFS$backends[backend]; wasmFS$backends[backend] = { - alloc_file: function(file) { - jsFileOps.alloc_file(file); + allocFile: function(file) { + jsFileOps.allocFile(file); return new Promise.resolve(); }, - free_file: function(file) { - jsFileOps.free_file(file); + freeFile: function(file) { + jsFileOps.freeFile(file); return new Promise.resolve(); }, write: function(file, buffer, length, offset) { diff --git a/src/library_wasmfs_js_file.js b/src/library_wasmfs_js_file.js index 5652376ac4619..46f15c2d20c8a 100644 --- a/src/library_wasmfs_js_file.js +++ b/src/library_wasmfs_js_file.js @@ -9,10 +9,10 @@ mergeInto(LibraryManager.library, { ], _wasmfs_create_js_file_backend_js: function(backend) { wasmFS$backends[backend] = { - alloc_file: function(file) { + allocFile: function(file) { // Do nothing: we allocate the typed array lazily, see write() }, - free_file: function(file) { + freeFile: function(file) { // Release the memory, as it now has no references to it any more. wasmFS$JSMemoryFiles[file] = undefined; }, diff --git a/system/lib/wasmfs/fetch_backend.cpp b/system/lib/wasmfs/fetch_backend.cpp index 995882e049798..f42734175d598 100644 --- a/system/lib/wasmfs/fetch_backend.cpp +++ b/system/lib/wasmfs/fetch_backend.cpp @@ -13,7 +13,7 @@ // See library_wasmfs_fetch.js extern "C" { -void _wasmfs_backend_add_fetch(wasmfs::backend_t); +void _wasmfs_create_fetch_backend_js(wasmfs::backend_t); } namespace wasmfs { @@ -21,7 +21,7 @@ namespace wasmfs { extern "C" backend_t wasmfs_create_fetch_backend(char* base_url) { // TODO: use base url, cache on JS side backend_t backend = wasmFS.addBackend(std::make_unique()); - _wasmfs_backend_add_fetch(backend); + _wasmfs_create_fetch_backend_js(backend); return backend; } diff --git a/tests/test_other.py b/tests/test_other.py index c3c19938805f7..ebf30be4b7bef 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -11436,6 +11436,15 @@ def test_wasmfs_jsfile_proxying_backend(self): self.set_setting('EXIT_RUNTIME') self.test_wasmfs_jsfile() + @node_pthreads + def test_wasmfs_fetch_backend(self): + self.emcc_args.append('-DFETCH') + self.emcc_args.append('--profiling') + self.set_setting('USE_PTHREADS') + self.set_setting('PROXY_TO_PTHREAD') + self.set_setting('EXIT_RUNTIME') + self.test_wasmfs_jsfile() + @disabled('Running with initial >2GB heaps is not currently supported on the CI version of Node') def test_hello_world_above_2gb(self): self.run_process([EMCC, test_file('hello_world.c'), '-sGLOBAL_BASE=2147483648', '-sINITIAL_MEMORY=3GB']) diff --git a/tests/wasmfs/wasmfs_jsfile.c b/tests/wasmfs/wasmfs_jsfile.c index 5e4c1a77bc802..7a2f3ee65c44f 100644 --- a/tests/wasmfs/wasmfs_jsfile.c +++ b/tests/wasmfs/wasmfs_jsfile.c @@ -34,13 +34,18 @@ static backend_t make_js_file_backend(void* arg) { return wasmfs_create_js_file_backend(); } -int main() { - backend_t backend; -#ifndef PROXYING - backend = make_js_file_backend(NULL); -#else - backend = wasmfs_create_proxied_backend(make_js_file_backend, NULL); +static backend_t make_backend() { +#ifdef PROXYING + return wasmfs_create_proxied_backend(make_js_file_backend, NULL); +#endif +#ifdef FETCH + return wasmfs_create_fetch_backend("TODO/url"); #endif + return make_js_file_backend(NULL); +} + +int main() { + backend_t backend = make_backend(); // Create a new backend file under root. int fd = wasmfs_create_file("/testfile", 0777, backend); From 52bbb0e1e36ebdb405e63c3d2ae17089f519b849 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 13:26:42 -0800 Subject: [PATCH 24/61] work [ci skip] --- src/library_wasmfs.js | 1 + system/lib/wasmfs/fetch_backend.cpp | 21 ++++++++++++++++--- .../wasmfs/proxied_async_js_impl_backend.h | 9 -------- tests/test_other.py | 1 + 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 0778ece46a083..af2281e7b5011 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -196,6 +196,7 @@ var WasmFSLibrary = { // we then connect to the calling C++. _wasmfs_jsimpl_async_alloc_file: function(backend, file, fptr, arg) { +console.log('alloc file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif diff --git a/system/lib/wasmfs/fetch_backend.cpp b/system/lib/wasmfs/fetch_backend.cpp index f42734175d598..37ce7cb454302 100644 --- a/system/lib/wasmfs/fetch_backend.cpp +++ b/system/lib/wasmfs/fetch_backend.cpp @@ -18,11 +18,26 @@ void _wasmfs_create_fetch_backend_js(wasmfs::backend_t); namespace wasmfs { +class FetchBackend : public Backend { + emscripten::SyncToAsync proxy; + +public: + FetchBackend() { + // Run the JS on the target thread. + proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { + _wasmfs_create_fetch_backend_js(this); + (*resume)(); + }); + } + + std::shared_ptr createFile(mode_t mode) override { + return std::make_shared(mode, this, proxy); + } +}; + extern "C" backend_t wasmfs_create_fetch_backend(char* base_url) { // TODO: use base url, cache on JS side - backend_t backend = wasmFS.addBackend(std::make_unique()); - _wasmfs_create_fetch_backend_js(backend); - return backend; + return wasmFS.addBackend(std::make_unique()); } } // namespace wasmfs diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 2b1cb5b1443e3..72c60922540ef 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -92,13 +92,4 @@ class ProxiedAsyncJSImplFile : public DataFile { } }; -class ProxiedAsyncJSImplBackend : public Backend { - emscripten::SyncToAsync proxy; - -public: - std::shared_ptr createFile(mode_t mode) override { - return std::make_shared(mode, this, proxy); - } -}; - } // namespace wasmfs diff --git a/tests/test_other.py b/tests/test_other.py index ebf30be4b7bef..884d56e1c462c 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -11440,6 +11440,7 @@ def test_wasmfs_jsfile_proxying_backend(self): def test_wasmfs_fetch_backend(self): self.emcc_args.append('-DFETCH') self.emcc_args.append('--profiling') + self.emcc_args.append('-sWASM=0') self.set_setting('USE_PTHREADS') self.set_setting('PROXY_TO_PTHREAD') self.set_setting('EXIT_RUNTIME') From 4c555bdf9ec385387c47b8bc278eb73223c24346 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 14:00:21 -0800 Subject: [PATCH 25/61] work --- src/library_wasmfs.js | 6 +- src/library_wasmfs_fetch.js | 65 ++++++++++--------- .../wasmfs/proxied_async_js_impl_backend.h | 64 +++++++++++++----- 3 files changed, 86 insertions(+), 49 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index af2281e7b5011..b08a0f541576f 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -226,11 +226,13 @@ console.log('alloc file async', backend); return wasmFS$backends[backend].read(file, buffer, length, offset); }, - _wasmfs_jsimpl_async_get_size: function(backend, file) { + _wasmfs_jsimpl_async_get_size: function(backend, file, fptr, arg) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - return wasmFS$backends[backend].getSize(file); + wasmFS$backends[backend].allocFile(file).then((size) => { + {{{ makeDynCall('vii', 'fptr') }}}(arg, size); + }); }, } diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index 2ed8c326f5b43..543a2e822c728 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -12,54 +12,57 @@ console.log('maek fetch', backend); // Start with the normal JSFile operations. __wasmfs_create_js_file_backend_js(backend); + function getFile() { + if (wasmFS$JSMemoryFiles[file]) { + // The data is already here, so nothing to do before we continue on to + // the actual read below. + return Promise.resolve(); + } + + // This is the first time we want the file's data. + // TODO: URL! + var url = 'data.dat'; + var promise; + if (typeof fetch === 'function') { + // On the web use fetch() normally. + promise = fetch().then((response) => { + if (!response['ok']) { + throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; + } + return response['arrayBuffer'](); + }); + } else { + // Until Node.js gets fetch support, use an async read. + promise = fs.readFile(url, "binary"); + } + return promise.then((buffer) => { + wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); + }); + } + // Add the async operations on top. var jsFileOps = wasmFS$backends[backend]; wasmFS$backends[backend] = { allocFile: function(file) { jsFileOps.allocFile(file); - return new Promise.resolve(); + return Promise.resolve(); }, freeFile: function(file) { jsFileOps.freeFile(file); - return new Promise.resolve(); + return Promise.resolve(); }, write: function(file, buffer, length, offset) { abort(); }, read: function(file, buffer, length, offset) { - var promise; - if (!wasmFS$JSMemoryFiles[file]) { - // This is the first read from this file, fetch it. - // TODO: URL! - var url = 'data.dat'; - if (typeof fetch === 'function') { - // On the web use fetch() normally. - promise = fetch().then((response) => { - if (!response['ok']) { - throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; - } - return response['arrayBuffer'](); - }); - } else { - // Until Node.js gets fetch support, use an async read. - promise = fs.readFile(url, "binary"); - } - promise.then((buffer) => { - wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); - }); - } else { - // The data is already here, so nothing to do before we continue on to - // the actual read below. - promise = Promise.resolve(); - } - - // Do the actual read. - return promise.then(() => { + return getFile().then(() => { return jsFileOps.read(file, buffer, length, offset); }); }, getSize: function(file) { - return new Promise.resolve(jsFileOps.getSize(file)); + return getFile().then(() => { + return jsFileOps.getSize(file); + }); }, }; }, diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 72c60922540ef..607127fd83e64 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -20,23 +20,42 @@ using js_index_t = uint32_t; +// Callbacks take a pointer to a CallbackState structure, which contains both the +// function to call to resume execution, and storage for any out params. +// Basically this stores the state during an async call. +struct CallbackState { + // The result of the operation, either success or an error code. + __wasi_errno_t result; + + // Some syscalls return an offset. + off_t offset; + + // The function to call to resume execution. + emscripten::SyncToAsync::Callback resume; + + CallbackState(emscripten::SyncToAsync::Callback resume) : resume(resume) {} +}; + + extern "C" { // JSImpl async API. -typedef void (*async_callback_t)(void*); -void _wasmfs_jsimpl_async_alloc_file(js_index_t backend, js_index_t index, async_callback_t callback, void* arg); -void _wasmfs_jsimpl_async_free_file(js_index_t backend, js_index_t index, async_callback_t callback, void* arg); -int _wasmfs_jsimpl_async_write(js_index_t backend, +// An async callback with no extra parameters. +typedef void (*async_callback_t)(CallbackState* state); + +void _wasmfs_jsimpl_async_alloc_file(js_index_t backend, js_index_t index, async_callback_t callback, CallbackState* state); +void _wasmfs_jsimpl_async_free_file(js_index_t backend, js_index_t index, async_callback_t callback, CallbackState* state); +void _wasmfs_jsimpl_async_write(js_index_t backend, js_index_t index, const uint8_t* buffer, size_t length, - off_t offset, async_callback_t callback, void* arg); -int _wasmfs_jsimpl_async_read(js_index_t backend, + off_t offset, async_callback_t callback, CallbackState* state); +void _wasmfs_jsimpl_async_read(js_index_t backend, js_index_t index, const uint8_t* buffer, size_t length, - off_t offset, async_callback_t callback, void* arg); -int _wasmfs_jsimpl_async_get_size(js_index_t backend, js_index_t index, async_callback_t callback, void* arg); + off_t offset, async_callback_t callback, CallbackState* state); +void _wasmfs_jsimpl_async_get_size(js_index_t backend, js_index_t index, async_callback_t callback, CallbackState* state); } namespace wasmfs { @@ -56,8 +75,9 @@ class ProxiedAsyncJSImplFile : public DataFile { __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { // TODO; proxy - return _wasmfs_jsimpl_async_write( + _wasmfs_jsimpl_async_write( getBackendIndex(), getFileIndex(), buf, len, offset, nullptr, nullptr); + return __WASI_ERRNO_SUCCESS; } __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { @@ -65,24 +85,36 @@ class ProxiedAsyncJSImplFile : public DataFile { // The caller should have already checked that the offset + len does // not exceed the file's size. assert(offset + len <= getSize()); - return _wasmfs_jsimpl_async_read( + _wasmfs_jsimpl_async_read( getBackendIndex(), getFileIndex(), buf, len, offset, nullptr, nullptr); + return __WASI_ERRNO_SUCCESS; } void flush() override {} size_t getSize() override { - // TODO; proxy - return _wasmfs_jsimpl_async_get_size(getBackendIndex(), getFileIndex(), nullptr, nullptr); + CallbackState state; + + proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { + state->resume = &resume; + _wasmfs_jsimpl_async_get_size(getBackendIndex(), getFileIndex(), [](CallbackState* state) { + (*state->resume)(); + }, &state); + }); + + return state->offset; } public: ProxiedAsyncJSImplFile(mode_t mode, backend_t backend, emscripten::SyncToAsync& proxy) : DataFile(mode, backend), proxy(proxy) { + CallbackState state(resume); + proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { - _wasmfs_jsimpl_async_alloc_file(getBackendIndex(), getFileIndex(), [](void* arg) { - auto* resume = (emscripten::SyncToAsync::Callback*)arg; - (**resume)(); - }, (void*)&resume); + state->resume = &resume; + + _wasmfs_jsimpl_async_alloc_file(getBackendIndex(), getFileIndex(), [](CallbackState* state) { + (*state->resume)(); + }, &state); }); } From 9321fe6136624787192f0731ec9daf831e521ecb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 14:00:35 -0800 Subject: [PATCH 26/61] format [ci skip] --- system/lib/wasmfs/fetch_backend.cpp | 2 +- system/lib/wasmfs/js_impl_backend.h | 4 +- .../wasmfs/proxied_async_js_impl_backend.h | 70 ++++++++++++------- 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/system/lib/wasmfs/fetch_backend.cpp b/system/lib/wasmfs/fetch_backend.cpp index 37ce7cb454302..72f15c51030ed 100644 --- a/system/lib/wasmfs/fetch_backend.cpp +++ b/system/lib/wasmfs/fetch_backend.cpp @@ -25,7 +25,7 @@ class FetchBackend : public Backend { FetchBackend() { // Run the JS on the target thread. proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { - _wasmfs_create_fetch_backend_js(this); + _wasmfs_create_fetch_backend_js(this); (*resume)(); }); } diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h index 4735c9acc1976..9e87eda51f9c3 100644 --- a/system/lib/wasmfs/js_impl_backend.h +++ b/system/lib/wasmfs/js_impl_backend.h @@ -96,9 +96,7 @@ class JSImplFile : public DataFile { _wasmfs_jsimpl_alloc_file(getBackendIndex(), getFileIndex()); } - ~JSImplFile() { - _wasmfs_jsimpl_free_file(getBackendIndex(), getFileIndex()); - } + ~JSImplFile() { _wasmfs_jsimpl_free_file(getBackendIndex(), getFileIndex()); } }; class JSImplBackend : public Backend { diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 607127fd83e64..9d5e928de96d0 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -20,8 +20,8 @@ using js_index_t = uint32_t; -// Callbacks take a pointer to a CallbackState structure, which contains both the -// function to call to resume execution, and storage for any out params. +// Callbacks take a pointer to a CallbackState structure, which contains both +// the function to call to resume execution, and storage for any out params. // Basically this stores the state during an async call. struct CallbackState { // The result of the operation, either success or an error code. @@ -36,26 +36,38 @@ struct CallbackState { CallbackState(emscripten::SyncToAsync::Callback resume) : resume(resume) {} }; - extern "C" { // JSImpl async API. // An async callback with no extra parameters. typedef void (*async_callback_t)(CallbackState* state); -void _wasmfs_jsimpl_async_alloc_file(js_index_t backend, js_index_t index, async_callback_t callback, CallbackState* state); -void _wasmfs_jsimpl_async_free_file(js_index_t backend, js_index_t index, async_callback_t callback, CallbackState* state); +void _wasmfs_jsimpl_async_alloc_file(js_index_t backend, + js_index_t index, + async_callback_t callback, + CallbackState* state); +void _wasmfs_jsimpl_async_free_file(js_index_t backend, + js_index_t index, + async_callback_t callback, + CallbackState* state); void _wasmfs_jsimpl_async_write(js_index_t backend, - js_index_t index, - const uint8_t* buffer, - size_t length, - off_t offset, async_callback_t callback, CallbackState* state); + js_index_t index, + const uint8_t* buffer, + size_t length, + off_t offset, + async_callback_t callback, + CallbackState* state); void _wasmfs_jsimpl_async_read(js_index_t backend, - js_index_t index, - const uint8_t* buffer, - size_t length, - off_t offset, async_callback_t callback, CallbackState* state); -void _wasmfs_jsimpl_async_get_size(js_index_t backend, js_index_t index, async_callback_t callback, CallbackState* state); + js_index_t index, + const uint8_t* buffer, + size_t length, + off_t offset, + async_callback_t callback, + CallbackState* state); +void _wasmfs_jsimpl_async_get_size(js_index_t backend, + js_index_t index, + async_callback_t callback, + CallbackState* state); } namespace wasmfs { @@ -74,14 +86,14 @@ class ProxiedAsyncJSImplFile : public DataFile { } __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { - // TODO; proxy + // TODO; proxy _wasmfs_jsimpl_async_write( getBackendIndex(), getFileIndex(), buf, len, offset, nullptr, nullptr); return __WASI_ERRNO_SUCCESS; } __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { - // TODO; proxy + // TODO; proxy // The caller should have already checked that the offset + len does // not exceed the file's size. assert(offset + len <= getSize()); @@ -97,30 +109,38 @@ class ProxiedAsyncJSImplFile : public DataFile { proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state->resume = &resume; - _wasmfs_jsimpl_async_get_size(getBackendIndex(), getFileIndex(), [](CallbackState* state) { - (*state->resume)(); - }, &state); + _wasmfs_jsimpl_async_get_size( + getBackendIndex(), + getFileIndex(), + [](CallbackState* state) { (*state->resume)(); }, + &state); }); return state->offset; } public: - ProxiedAsyncJSImplFile(mode_t mode, backend_t backend, emscripten::SyncToAsync& proxy) : DataFile(mode, backend), proxy(proxy) { + ProxiedAsyncJSImplFile(mode_t mode, + backend_t backend, + emscripten::SyncToAsync& proxy) + : DataFile(mode, backend), proxy(proxy) { CallbackState state(resume); proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state->resume = &resume; - _wasmfs_jsimpl_async_alloc_file(getBackendIndex(), getFileIndex(), [](CallbackState* state) { - (*state->resume)(); - }, &state); + _wasmfs_jsimpl_async_alloc_file( + getBackendIndex(), + getFileIndex(), + [](CallbackState* state) { (*state->resume)(); }, + &state); }); } ~ProxiedAsyncJSImplFile() { - // TODO; proxy - _wasmfs_jsimpl_async_free_file(getBackendIndex(), getFileIndex(), nullptr, nullptr); + // TODO; proxy + _wasmfs_jsimpl_async_free_file( + getBackendIndex(), getFileIndex(), nullptr, nullptr); } }; From 55928de1aea4c71e92dac86f52be99eb54f90b76 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 14:03:00 -0800 Subject: [PATCH 27/61] c++ builds again [ci skip] --- system/lib/wasmfs/proxied_async_js_impl_backend.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 9d5e928de96d0..8769f1c7f11b7 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -32,8 +32,6 @@ struct CallbackState { // The function to call to resume execution. emscripten::SyncToAsync::Callback resume; - - CallbackState(emscripten::SyncToAsync::Callback resume) : resume(resume) {} }; extern "C" { @@ -108,7 +106,7 @@ class ProxiedAsyncJSImplFile : public DataFile { CallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { - state->resume = &resume; + state.resume = resume; _wasmfs_jsimpl_async_get_size( getBackendIndex(), getFileIndex(), @@ -116,7 +114,7 @@ class ProxiedAsyncJSImplFile : public DataFile { &state); }); - return state->offset; + return state.offset; } public: @@ -124,10 +122,10 @@ class ProxiedAsyncJSImplFile : public DataFile { backend_t backend, emscripten::SyncToAsync& proxy) : DataFile(mode, backend), proxy(proxy) { - CallbackState state(resume); + CallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { - state->resume = &resume; + state.resume = resume; _wasmfs_jsimpl_async_alloc_file( getBackendIndex(), From 56240d7229f0b61f1f6aee65f90824f8df0ed067 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 14:05:55 -0800 Subject: [PATCH 28/61] progress [ci skip] --- src/library_wasmfs.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index b08a0f541576f..43618e23e2717 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -200,7 +200,9 @@ console.log('alloc file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif + {{{ runtimeKeepalivePush() }}} wasmFS$backends[backend].allocFile(file).then(() => { + {{{ runtimeKeepalivePop() }}} {{{ makeDynCall('vii', 'fptr') }}}(arg); }); }, @@ -230,7 +232,9 @@ console.log('alloc file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif + {{{ runtimeKeepalivePush() }}} wasmFS$backends[backend].allocFile(file).then((size) => { + {{{ runtimeKeepalivePop() }}} {{{ makeDynCall('vii', 'fptr') }}}(arg, size); }); }, From 3e71d34241901d560bc0e6625ef0e3ed05e5c024 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 14:55:30 -0800 Subject: [PATCH 29/61] progress [ci skip] --- src/library_wasmfs.js | 6 +++++- src/library_wasmfs_fetch.js | 20 +++++++++++++++----- tests/test_other.py | 1 + 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 43618e23e2717..3d012b55f48ec 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -208,6 +208,7 @@ console.log('alloc file async', backend); }, _wasmfs_jsimpl_async_free_file: function(backend, file) { +console.log('free file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif @@ -215,6 +216,7 @@ console.log('alloc file async', backend); }, _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, offset) { +console.log('write file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif @@ -222,6 +224,7 @@ console.log('alloc file async', backend); }, _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offset) { +console.log('read file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif @@ -229,11 +232,12 @@ console.log('alloc file async', backend); }, _wasmfs_jsimpl_async_get_size: function(backend, file, fptr, arg) { +console.log('getSize file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].allocFile(file).then((size) => { + wasmFS$backends[backend].getSize(file).then((size) => { {{{ runtimeKeepalivePop() }}} {{{ makeDynCall('vii', 'fptr') }}}(arg, size); }); diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index 543a2e822c728..b9b87a241ac0e 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -12,7 +12,7 @@ console.log('maek fetch', backend); // Start with the normal JSFile operations. __wasmfs_create_js_file_backend_js(backend); - function getFile() { + function getFile(file) { if (wasmFS$JSMemoryFiles[file]) { // The data is already here, so nothing to do before we continue on to // the actual read below. @@ -33,7 +33,15 @@ console.log('maek fetch', backend); }); } else { // Until Node.js gets fetch support, use an async read. - promise = fs.readFile(url, "binary"); + console.log('node1'); + promise = new Promise((resolve, reject) => { + console.log('node2'); + fs.readFile(url, "binary", (err, data) => { + console.log('node3', err, data); + if (err) reject(); + resolve(data); + }); + }); } return promise.then((buffer) => { wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); @@ -55,13 +63,15 @@ console.log('maek fetch', backend); abort(); }, read: function(file, buffer, length, offset) { - return getFile().then(() => { + return getFile(file).then(() => { return jsFileOps.read(file, buffer, length, offset); }); }, getSize: function(file) { - return getFile().then(() => { - return jsFileOps.getSize(file); + return getFile(file).then(() => { + var ret = jsFileOps.getSize(file); + console.log('sizeey', ret); + return ret; }); }, }; diff --git a/tests/test_other.py b/tests/test_other.py index 884d56e1c462c..d38dc0ff7b346 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -11441,6 +11441,7 @@ def test_wasmfs_fetch_backend(self): self.emcc_args.append('-DFETCH') self.emcc_args.append('--profiling') self.emcc_args.append('-sWASM=0') + self.emcc_args.append('-O2') self.set_setting('USE_PTHREADS') self.set_setting('PROXY_TO_PTHREAD') self.set_setting('EXIT_RUNTIME') From 736424f6da7330da177ecf0d4c508465204d46f6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 15:10:35 -0800 Subject: [PATCH 30/61] progress [ci skip] --- src/library.js | 1 - src/library_wasmfs_fetch.js | 14 +++++++++----- tests/test_browser.py | 4 ++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/library.js b/src/library.js index e415c6a012fe3..6da3395c60b47 100644 --- a/src/library.js +++ b/src/library.js @@ -3648,7 +3648,6 @@ LibraryManager.library = { return x.indexOf('dynCall_') == 0 || unmangledSymbols.includes(x) ? x : '_' + x; }, - $asyncLoad__docs: '/** @param {boolean=} noRunDep */', $asyncLoad: function(url, onload, onerror, noRunDep) { var dep = !noRunDep ? getUniqueRunDependency('al ' + url) : ''; diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index b9b87a241ac0e..64c9e9b08bd22 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -8,7 +8,7 @@ mergeInto(LibraryManager.library, { '_wasmfs_create_js_file_backend_js', ], _wasmfs_create_fetch_backend_js: function(backend) { -console.log('maek fetch', backend); +console.log(['maek fetch', backend] + '\n'); // Start with the normal JSFile operations. __wasmfs_create_js_file_backend_js(backend); @@ -33,16 +33,20 @@ console.log('maek fetch', backend); }); } else { // Until Node.js gets fetch support, use an async read. - console.log('node1'); + console.log(['node1', backend] + '\n'); promise = new Promise((resolve, reject) => { - console.log('node2'); + console.log(['node2', backend] + '\n'); fs.readFile(url, "binary", (err, data) => { - console.log('node3', err, data); + console.log(['node3', err, data, backend] + '\n'); + throw 'waka'; if (err) reject(); resolve(data); }); + console.log(['node2a'] + '\n'); }); + console.log(['node2b'] + '\n'); } + console.log(['promise', promise] + '\n'); return promise.then((buffer) => { wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); }); @@ -70,7 +74,7 @@ console.log('maek fetch', backend); getSize: function(file) { return getFile(file).then(() => { var ret = jsFileOps.getSize(file); - console.log('sizeey', ret); + console.log(['sizeey', ret] + '\n'); return ret; }); }, diff --git a/tests/test_browser.py b/tests/test_browser.py index 945f3ccf57cf2..9c63ef5142c72 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5179,6 +5179,10 @@ def test(args): test(['-sMALLOC=emmalloc-memvalidate']) test(['-sMALLOC=emmalloc-memvalidate-verbose']) + def test_wasmfs_fetch_backend(self): + self.btest_exit(test_file('wasmfs/wasmfs_jsfile.c'), + args=['-sWASMFS', '-DFETCH', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD']) + @no_firefox('no 4GB support yet') def test_zzz_zzz_emmalloc_memgrowth(self, *args): self.btest(test_file('browser/emmalloc_memgrowth.cpp'), expected='0', args=['-sMALLOC=emmalloc', '-sALLOW_MEMORY_GROWTH=1', '-sABORTING_MALLOC=0', '-sASSERTIONS=2', '-sMINIMAL_RUNTIME=1', '-sMAXIMUM_MEMORY=4GB']) From e33e58be72f8e3b464dcf3cd78e937555a516ccd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 15:17:26 -0800 Subject: [PATCH 31/61] progress [ci skip] --- src/library_wasmfs_fetch.js | 2 +- tests/test_browser.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index 64c9e9b08bd22..baf00d2e376a2 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -25,7 +25,7 @@ console.log(['maek fetch', backend] + '\n'); var promise; if (typeof fetch === 'function') { // On the web use fetch() normally. - promise = fetch().then((response) => { + promise = fetch(url).then((response) => { if (!response['ok']) { throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; } diff --git a/tests/test_browser.py b/tests/test_browser.py index 9c63ef5142c72..994e7fcd075d5 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5180,8 +5180,9 @@ def test(args): test(['-sMALLOC=emmalloc-memvalidate-verbose']) def test_wasmfs_fetch_backend(self): - self.btest_exit(test_file('wasmfs/wasmfs_jsfile.c'), - args=['-sWASMFS', '-DFETCH', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD']) + create_file('data.dat', 'hello, fetch') + self.btest_exit(test_file('wasmfs/wasmfs_fetch.c'), + args=['-sWASMFS', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD']) @no_firefox('no 4GB support yet') def test_zzz_zzz_emmalloc_memgrowth(self, *args): From 2158d61b82982ce066a25741e1f6d6ae0961e118 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 15:27:07 -0800 Subject: [PATCH 32/61] progress [ci skip] --- tests/test_browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index 994e7fcd075d5..7e6bcd6e4e2b8 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5182,7 +5182,7 @@ def test(args): def test_wasmfs_fetch_backend(self): create_file('data.dat', 'hello, fetch') self.btest_exit(test_file('wasmfs/wasmfs_fetch.c'), - args=['-sWASMFS', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD']) + args=['-sWASMFS', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD', '--profiling']) @no_firefox('no 4GB support yet') def test_zzz_zzz_emmalloc_memgrowth(self, *args): From 57714f49f2cd66e3e7e04ca3ae99a3e64718c006 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 15:53:35 -0800 Subject: [PATCH 33/61] progress [ci skip] --- src/library_wasmfs.js | 19 ++++++++++++++----- .../wasmfs/proxied_async_js_impl_backend.h | 4 ++++ tests/browser_reporting.js | 2 +- tests/test_browser.py | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 3d012b55f48ec..4a7118a2ca984 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -207,7 +207,7 @@ console.log('alloc file async', backend); }); }, - _wasmfs_jsimpl_async_free_file: function(backend, file) { + _wasmfs_jsimpl_async_free_file: function(backend, file, fptr, arg) { console.log('free file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); @@ -215,7 +215,7 @@ console.log('free file async', backend); return wasmFS$backends[backend].freeFile(file); }, - _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, offset) { + _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, offset, fptr, arg) { console.log('write file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); @@ -223,12 +223,17 @@ console.log('write file async', backend); return wasmFS$backends[backend].write(file, buffer, length, offset); }, - _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offset) { + _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offset, fptr, arg) { console.log('read file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - return wasmFS$backends[backend].read(file, buffer, length, offset); + {{{ runtimeKeepalivePush() }}} + wasmFS$backends[backend].read(file, buffer, length, offset).then((size) => { + {{{ runtimeKeepalivePop() }}} +alert('HEAP32!'); + {{{ makeDynCall('vii', 'fptr') }}}(arg); + }); }, _wasmfs_jsimpl_async_get_size: function(backend, file, fptr, arg) { @@ -239,7 +244,11 @@ console.log('getSize file async', backend); {{{ runtimeKeepalivePush() }}} wasmFS$backends[backend].getSize(file).then((size) => { {{{ runtimeKeepalivePop() }}} - {{{ makeDynCall('vii', 'fptr') }}}(arg, size); +console.log('waka get size response ' + size, 'writing to', arg + 8); + HEAP32[arg >> 2] = 0; // success + HEAP32[arg + 8 >> 2] = size; + HEAP32[arg + 12 >> 2] = 0; + {{{ makeDynCall('vii', 'fptr') }}}(arg); }); }, } diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 8769f1c7f11b7..8799d6f8d6f07 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -103,10 +103,14 @@ class ProxiedAsyncJSImplFile : public DataFile { void flush() override {} size_t getSize() override { +EM_ASM({ console.error("getSize() C++") }); CallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state.resume = resume; + +EM_ASM({ console.error("getSIze() C++ offset ptr " + $0) }, (uint32_t)&state.offset); + _wasmfs_jsimpl_async_get_size( getBackendIndex(), getFileIndex(), diff --git a/tests/browser_reporting.js b/tests/browser_reporting.js index c96fa7c3112ac..f93ddf83b7561 100644 --- a/tests/browser_reporting.js +++ b/tests/browser_reporting.js @@ -20,7 +20,7 @@ function reportResultToServer(result, sync, port) { xhr.send(); if (typeof window === 'object' && window && hasModule && !Module['pageThrewException']) { /* for easy debugging, don't close window on failure */ - setTimeout(function() { window.close() }, 1000); +// setTimeout(function() { window.close() }, 1000); } } } diff --git a/tests/test_browser.py b/tests/test_browser.py index 7e6bcd6e4e2b8..23138943d3737 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5182,7 +5182,7 @@ def test(args): def test_wasmfs_fetch_backend(self): create_file('data.dat', 'hello, fetch') self.btest_exit(test_file('wasmfs/wasmfs_fetch.c'), - args=['-sWASMFS', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD', '--profiling']) + args=['-sWASMFS', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD', '--profiling', '-O0']) @no_firefox('no 4GB support yet') def test_zzz_zzz_emmalloc_memgrowth(self, *args): From 337a777dd219a3ce533902a7d4a0210cccd39d0a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 15:54:50 -0800 Subject: [PATCH 34/61] progress [ci skip] --- src/library_wasmfs.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 4a7118a2ca984..8805ee6de7030 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -231,7 +231,9 @@ console.log('read file async', backend); {{{ runtimeKeepalivePush() }}} wasmFS$backends[backend].read(file, buffer, length, offset).then((size) => { {{{ runtimeKeepalivePop() }}} -alert('HEAP32!'); + HEAP32[arg >> 2] = 0; // success + HEAP32[arg + 8 >> 2] = size; + HEAP32[arg + 12 >> 2] = 0; {{{ makeDynCall('vii', 'fptr') }}}(arg); }); }, From 85b596737c5d329b21f4a38a4bd5ebf4cbebdc7b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 8 Feb 2022 16:05:00 -0800 Subject: [PATCH 35/61] test passes [ci skip] --- src/library_wasmfs.js | 24 ++++---- .../wasmfs/proxied_async_js_impl_backend.h | 57 ++++++++++++++----- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 8805ee6de7030..34381e99b845a 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -203,7 +203,7 @@ console.log('alloc file async', backend); {{{ runtimeKeepalivePush() }}} wasmFS$backends[backend].allocFile(file).then(() => { {{{ runtimeKeepalivePop() }}} - {{{ makeDynCall('vii', 'fptr') }}}(arg); + {{{ makeDynCall('vi', 'fptr') }}}(arg); }); }, @@ -212,29 +212,29 @@ console.log('free file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif - return wasmFS$backends[backend].freeFile(file); + {{{ runtimeKeepalivePush() }}} + wasmFS$backends[backend].freeFile(file).then(() => { + {{{ runtimeKeepalivePop() }}} + {{{ makeDynCall('vi', 'fptr') }}}(arg); + }); }, - _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, offset, fptr, arg) { -console.log('write file async', backend); -#if ASSERTIONS - assert(wasmFS$backends[backend]); -#endif - return wasmFS$backends[backend].write(file, buffer, length, offset); + _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, offsetLow, offsetHigh, fptr, arg) { + abort('TODO'); }, - _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offset, fptr, arg) { + _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offsetLow, offsetHigh, fptr, arg) { console.log('read file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].read(file, buffer, length, offset).then((size) => { + wasmFS$backends[backend].read(file, buffer, length, offsetLow /* FIXME */).then((size) => { {{{ runtimeKeepalivePop() }}} HEAP32[arg >> 2] = 0; // success HEAP32[arg + 8 >> 2] = size; HEAP32[arg + 12 >> 2] = 0; - {{{ makeDynCall('vii', 'fptr') }}}(arg); + {{{ makeDynCall('vi', 'fptr') }}}(arg); }); }, @@ -250,7 +250,7 @@ console.log('waka get size response ' + size, 'writing to', arg + 8); HEAP32[arg >> 2] = 0; // success HEAP32[arg + 8 >> 2] = size; HEAP32[arg + 12 >> 2] = 0; - {{{ makeDynCall('vii', 'fptr') }}}(arg); + {{{ makeDynCall('vi', 'fptr') }}}(arg); }); }, } diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 8799d6f8d6f07..b298f1874ad2d 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -84,10 +84,22 @@ class ProxiedAsyncJSImplFile : public DataFile { } __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { - // TODO; proxy - _wasmfs_jsimpl_async_write( - getBackendIndex(), getFileIndex(), buf, len, offset, nullptr, nullptr); - return __WASI_ERRNO_SUCCESS; + CallbackState state; + + proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { + state.resume = resume; + + _wasmfs_jsimpl_async_write( + getBackendIndex(), + getFileIndex(), + buf, + len, + offset, + [](CallbackState* state) { (*state->resume)(); }, + &state); + }); + + return state.result; } __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { @@ -95,22 +107,33 @@ class ProxiedAsyncJSImplFile : public DataFile { // The caller should have already checked that the offset + len does // not exceed the file's size. assert(offset + len <= getSize()); - _wasmfs_jsimpl_async_read( - getBackendIndex(), getFileIndex(), buf, len, offset, nullptr, nullptr); - return __WASI_ERRNO_SUCCESS; + + CallbackState state; + + proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { + state.resume = resume; + + _wasmfs_jsimpl_async_read( + getBackendIndex(), + getFileIndex(), + buf, + len, + offset, + [](CallbackState* state) { (*state->resume)(); }, + &state); + }); + + return state.result; } void flush() override {} size_t getSize() override { -EM_ASM({ console.error("getSize() C++") }); CallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state.resume = resume; -EM_ASM({ console.error("getSIze() C++ offset ptr " + $0) }, (uint32_t)&state.offset); - _wasmfs_jsimpl_async_get_size( getBackendIndex(), getFileIndex(), @@ -140,9 +163,17 @@ EM_ASM({ console.error("getSIze() C++ offset ptr " + $0) }, (uint32_t)&state.off } ~ProxiedAsyncJSImplFile() { - // TODO; proxy - _wasmfs_jsimpl_async_free_file( - getBackendIndex(), getFileIndex(), nullptr, nullptr); + CallbackState state; + + proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { + state.resume = resume; + + _wasmfs_jsimpl_async_free_file( + getBackendIndex(), + getFileIndex(), + [](CallbackState* state) { (*state->resume)(); }, + &state); + }); } }; From cbe113bc3effe79037066e907c2746a4b4738653 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 08:08:44 -0800 Subject: [PATCH 36/61] work [ci skip] --- src/struct_info_internal.json | 9 ++++ system/lib/wasmfs/async_callback.h | 31 ++++++++++++ .../wasmfs/proxied_async_js_impl_backend.h | 47 +++++++++---------- tools/gen_struct_info.py | 1 + 4 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 system/lib/wasmfs/async_callback.h diff --git a/src/struct_info_internal.json b/src/struct_info_internal.json index 73449dbb30bf7..366a3f7355a1a 100644 --- a/src/struct_info_internal.json +++ b/src/struct_info_internal.json @@ -47,5 +47,14 @@ "name" ] } + }, + { + "file": "proxied_async_js_impl_backend.h", + "structs": { + "CallbackState": [ + "result", + "offset" + ] + } } ] diff --git a/system/lib/wasmfs/async_callback.h b/system/lib/wasmfs/async_callback.h new file mode 100644 index 0000000000000..3063143411457 --- /dev/null +++ b/system/lib/wasmfs/async_callback.h @@ -0,0 +1,31 @@ +// Copyright 2022 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, the MIT license and the +// University of Illinois/NCSA Open Source License. Both these licenses can be +// found in the LICENSE file. + +// This file defines the JS file backend and JS file of the new file system. +// Current Status: Work in Progress. +// See https://github.com/emscripten-core/emscripten/issues/15041. + +#pragma once + +#include "backend.h" +#include "thread_utils.h" +#include "wasmfs.h" +#include "async_callback.h" + +// Callbacks for the async API between C and JS. This is declared in a small +// separate header for convenience of gen_struct_info. + +// Callbacks take a pointer to a CallbackState structure, which contains both +// the function to call to resume execution, and storage for any out params. +// Basically this stores the state during an async call. +struct CallbackState { + // The result of the operation, either success or an error code. + __wasi_errno_t result; + + // Some syscalls return an offset. + off_t offset; +}; + +} // namespace wasmfs diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index b298f1874ad2d..38383429ca59f 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -12,6 +12,7 @@ #include "backend.h" #include "thread_utils.h" #include "wasmfs.h" +#include "async_callback.h" // // Similar to JSImplBackend, but proxies to another thread where the JS can be @@ -20,16 +21,10 @@ using js_index_t = uint32_t; -// Callbacks take a pointer to a CallbackState structure, which contains both -// the function to call to resume execution, and storage for any out params. -// Basically this stores the state during an async call. -struct CallbackState { - // The result of the operation, either success or an error code. - __wasi_errno_t result; - - // Some syscalls return an offset. - off_t offset; - +// A callback state that also adds the C++ component for resuming. This is only +// needed on the C++ side, but needs to be passed around as part of this +// struct when we go through C and JS> +struct CppCallbackState : public CallbackState { // The function to call to resume execution. emscripten::SyncToAsync::Callback resume; }; @@ -38,34 +33,34 @@ extern "C" { // JSImpl async API. // An async callback with no extra parameters. -typedef void (*async_callback_t)(CallbackState* state); +typedef void (*async_callback_t)(CppCallbackState* state); void _wasmfs_jsimpl_async_alloc_file(js_index_t backend, js_index_t index, async_callback_t callback, - CallbackState* state); + CppCallbackState* state); void _wasmfs_jsimpl_async_free_file(js_index_t backend, js_index_t index, async_callback_t callback, - CallbackState* state); + CppCallbackState* state); void _wasmfs_jsimpl_async_write(js_index_t backend, js_index_t index, const uint8_t* buffer, size_t length, off_t offset, async_callback_t callback, - CallbackState* state); + CppCallbackState* state); void _wasmfs_jsimpl_async_read(js_index_t backend, js_index_t index, const uint8_t* buffer, size_t length, off_t offset, async_callback_t callback, - CallbackState* state); + CppCallbackState* state); void _wasmfs_jsimpl_async_get_size(js_index_t backend, js_index_t index, async_callback_t callback, - CallbackState* state); + CppCallbackState* state); } namespace wasmfs { @@ -84,7 +79,7 @@ class ProxiedAsyncJSImplFile : public DataFile { } __wasi_errno_t write(const uint8_t* buf, size_t len, off_t offset) override { - CallbackState state; + CppCallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state.resume = resume; @@ -95,7 +90,7 @@ class ProxiedAsyncJSImplFile : public DataFile { buf, len, offset, - [](CallbackState* state) { (*state->resume)(); }, + [](CppCallbackState* state) { (*state->resume)(); }, &state); }); @@ -108,7 +103,7 @@ class ProxiedAsyncJSImplFile : public DataFile { // not exceed the file's size. assert(offset + len <= getSize()); - CallbackState state; + CppCallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state.resume = resume; @@ -119,7 +114,7 @@ class ProxiedAsyncJSImplFile : public DataFile { buf, len, offset, - [](CallbackState* state) { (*state->resume)(); }, + [](CppCallbackState* state) { (*state->resume)(); }, &state); }); @@ -129,7 +124,7 @@ class ProxiedAsyncJSImplFile : public DataFile { void flush() override {} size_t getSize() override { - CallbackState state; + CppCallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state.resume = resume; @@ -137,7 +132,7 @@ class ProxiedAsyncJSImplFile : public DataFile { _wasmfs_jsimpl_async_get_size( getBackendIndex(), getFileIndex(), - [](CallbackState* state) { (*state->resume)(); }, + [](CppCallbackState* state) { (*state->resume)(); }, &state); }); @@ -149,7 +144,7 @@ class ProxiedAsyncJSImplFile : public DataFile { backend_t backend, emscripten::SyncToAsync& proxy) : DataFile(mode, backend), proxy(proxy) { - CallbackState state; + CppCallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state.resume = resume; @@ -157,13 +152,13 @@ class ProxiedAsyncJSImplFile : public DataFile { _wasmfs_jsimpl_async_alloc_file( getBackendIndex(), getFileIndex(), - [](CallbackState* state) { (*state->resume)(); }, + [](CppCallbackState* state) { (*state->resume)(); }, &state); }); } ~ProxiedAsyncJSImplFile() { - CallbackState state; + CppCallbackState state; proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { state.resume = resume; @@ -171,7 +166,7 @@ class ProxiedAsyncJSImplFile : public DataFile { _wasmfs_jsimpl_async_free_file( getBackendIndex(), getFileIndex(), - [](CallbackState* state) { (*state->resume)(); }, + [](CppCallbackState* state) { (*state->resume)(); }, &state); }); } diff --git a/tools/gen_struct_info.py b/tools/gen_struct_info.py index ccb8e40698856..098a7373bc58d 100755 --- a/tools/gen_struct_info.py +++ b/tools/gen_struct_info.py @@ -430,6 +430,7 @@ def main(args): '-I' + utils.path_from_root('system/lib/libc/musl/src/internal'), '-I' + utils.path_from_root('system/lib/libc/musl/src/include'), '-I' + utils.path_from_root('system/lib/pthread/'), + '-I' + utils.path_from_root('system/lib/wasmfs/'), ] cxxflags = [ From 223485a32bdd2b3175213a9060ca695c1f557c3e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 08:09:56 -0800 Subject: [PATCH 37/61] work [ci skip] --- src/struct_info_internal.json | 2 +- system/lib/wasmfs/async_callback.h | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/struct_info_internal.json b/src/struct_info_internal.json index 366a3f7355a1a..5a8516feef486 100644 --- a/src/struct_info_internal.json +++ b/src/struct_info_internal.json @@ -49,7 +49,7 @@ } }, { - "file": "proxied_async_js_impl_backend.h", + "file": "async_callback.h", "structs": { "CallbackState": [ "result", diff --git a/system/lib/wasmfs/async_callback.h b/system/lib/wasmfs/async_callback.h index 3063143411457..3aed2ac9107ec 100644 --- a/system/lib/wasmfs/async_callback.h +++ b/system/lib/wasmfs/async_callback.h @@ -9,10 +9,8 @@ #pragma once -#include "backend.h" -#include "thread_utils.h" -#include "wasmfs.h" -#include "async_callback.h" +#include "wasi/api.h" +#include "sys/types.h" // Callbacks for the async API between C and JS. This is declared in a small // separate header for convenience of gen_struct_info. @@ -28,4 +26,3 @@ struct CallbackState { off_t offset; }; -} // namespace wasmfs From 0cb056019fa82f67f4840eebe2a5e8beff3b7839 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 08:11:49 -0800 Subject: [PATCH 38/61] work [ci skip] --- tests/wasmfs/wasmfs_fetch.c | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/wasmfs/wasmfs_fetch.c diff --git a/tests/wasmfs/wasmfs_fetch.c b/tests/wasmfs/wasmfs_fetch.c new file mode 100644 index 0000000000000..e0e1af4f4aa6d --- /dev/null +++ b/tests/wasmfs/wasmfs_fetch.c @@ -0,0 +1,51 @@ +/* + * Copyright 2021 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static backend_t make_js_file_backend(void* arg) { + return wasmfs_create_js_file_backend(); +} + +int main() { + backend_t backend = wasmfs_create_fetch_backend("TODO/url"); + + // Create a file in that backend. + int fd = wasmfs_create_file("/testfile", 0777, backend); + + // Get the size of the file. This will cause a transparent fetch of the data, + // after which the size is > 0 + struct stat file; + assert(fstat(fd, &file) != -1); + printf("file size: %lld\n", file.st_size); + assert(file.st_size > 0); + + // Create a second file. + int fd2 = wasmfs_create_file("/testfile2", 0777, backend); + + // Read the data from the file. As before, when we need the data it is + // transparently fetched for us. + char buf[file.st_size]; + int bytes_read = read(fd, buf, file.st_size); + printf("read size: %d\n", bytes_read); + assert(errno == 0); + buf[bytes_read] = 0; + printf("%s\n", buf); + assert(strcmp(buf, "hello, fetch") == 0); + + return 0; +} From 2819980b466d1c0e9b4b0704c57360519b7ac1ce Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 08:13:03 -0800 Subject: [PATCH 39/61] work [ci skip] --- src/library_wasmfs_fetch.js | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index baf00d2e376a2..bf3493088a4cd 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -8,7 +8,6 @@ mergeInto(LibraryManager.library, { '_wasmfs_create_js_file_backend_js', ], _wasmfs_create_fetch_backend_js: function(backend) { -console.log(['maek fetch', backend] + '\n'); // Start with the normal JSFile operations. __wasmfs_create_js_file_backend_js(backend); @@ -22,32 +21,12 @@ console.log(['maek fetch', backend] + '\n'); // This is the first time we want the file's data. // TODO: URL! var url = 'data.dat'; - var promise; - if (typeof fetch === 'function') { - // On the web use fetch() normally. - promise = fetch(url).then((response) => { - if (!response['ok']) { - throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; - } - return response['arrayBuffer'](); - }); - } else { - // Until Node.js gets fetch support, use an async read. - console.log(['node1', backend] + '\n'); - promise = new Promise((resolve, reject) => { - console.log(['node2', backend] + '\n'); - fs.readFile(url, "binary", (err, data) => { - console.log(['node3', err, data, backend] + '\n'); - throw 'waka'; - if (err) reject(); - resolve(data); - }); - console.log(['node2a'] + '\n'); - }); - console.log(['node2b'] + '\n'); - } - console.log(['promise', promise] + '\n'); - return promise.then((buffer) => { + return fetch(url).then((response) => { + if (!response['ok']) { + throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; + } + return response['arrayBuffer'](); + }).then((buffer) => { wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); }); } From 5dc1455a4d64386871b53649bbe740aaa3aaceb6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 08:18:10 -0800 Subject: [PATCH 40/61] work [ci skip] --- src/library_wasmfs_fetch.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index bf3493088a4cd..c428559fdcc15 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -1,6 +1,7 @@ mergeInto(LibraryManager.library, { - // Fetch backend: On first read, does a fetch() to get the data. It then - // behaves like JSFile + // Fetch backend: On first access of the file (either a read or a getSize), it + // will fetch() the data from the network asynchronously. Otherwise, after + // that fetch it behaves just like JSFile (and it reuses the code from there). _wasmfs_create_fetch_backend_js__deps: [ '$wasmFS$backends', @@ -8,9 +9,8 @@ mergeInto(LibraryManager.library, { '_wasmfs_create_js_file_backend_js', ], _wasmfs_create_fetch_backend_js: function(backend) { - // Start with the normal JSFile operations. - __wasmfs_create_js_file_backend_js(backend); - + // Get a promise that fetches the data and stores it in JS memory (if it has + // not already been fetched). function getFile(file) { if (wasmFS$JSMemoryFiles[file]) { // The data is already here, so nothing to do before we continue on to @@ -19,7 +19,7 @@ mergeInto(LibraryManager.library, { } // This is the first time we want the file's data. - // TODO: URL! + // TODO: real URL! var url = 'data.dat'; return fetch(url).then((response) => { if (!response['ok']) { @@ -31,9 +31,16 @@ mergeInto(LibraryManager.library, { }); } + // Start with the normal JSFile operations. This sets + // wasmFS$backends[backend] + // which we will then augment. + __wasmfs_create_js_file_backend_js(backend); + // Add the async operations on top. var jsFileOps = wasmFS$backends[backend]; wasmFS$backends[backend] = { + // alloc/free operations are not actually async. Just forward to the + // parent class, but we must return a Promise as the caller expects. allocFile: function(file) { jsFileOps.allocFile(file); return Promise.resolve(); @@ -42,9 +49,12 @@ mergeInto(LibraryManager.library, { jsFileOps.freeFile(file); return Promise.resolve(); }, + write: function(file, buffer, length, offset) { - abort(); + abort("TODO: file writing in fetch backend? read-only for now"); }, + + // read/getSize fetch the data, then forward to the parent class. read: function(file, buffer, length, offset) { return getFile(file).then(() => { return jsFileOps.read(file, buffer, length, offset); @@ -52,9 +62,7 @@ mergeInto(LibraryManager.library, { }, getSize: function(file) { return getFile(file).then(() => { - var ret = jsFileOps.getSize(file); - console.log(['sizeey', ret] + '\n'); - return ret; + return jsFileOps.getSize(file); }); }, }; From db018cad061fc89a7f972b7e516c59f53fdd66b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:06:24 -0800 Subject: [PATCH 41/61] proper [ci skip] --- src/library_wasmfs.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 34381e99b845a..fa5fc722e3569 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -231,9 +231,8 @@ console.log('read file async', backend); {{{ runtimeKeepalivePush() }}} wasmFS$backends[backend].read(file, buffer, length, offsetLow /* FIXME */).then((size) => { {{{ runtimeKeepalivePop() }}} - HEAP32[arg >> 2] = 0; // success - HEAP32[arg + 8 >> 2] = size; - HEAP32[arg + 12 >> 2] = 0; + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; {{{ makeDynCall('vi', 'fptr') }}}(arg); }); }, @@ -246,10 +245,8 @@ console.log('getSize file async', backend); {{{ runtimeKeepalivePush() }}} wasmFS$backends[backend].getSize(file).then((size) => { {{{ runtimeKeepalivePop() }}} -console.log('waka get size response ' + size, 'writing to', arg + 8); - HEAP32[arg >> 2] = 0; // success - HEAP32[arg + 8 >> 2] = size; - HEAP32[arg + 12 >> 2] = 0; + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; {{{ makeDynCall('vi', 'fptr') }}}(arg); }); }, From b59cc11d39ddc07aba3f054fb521817fb23f1d2e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:06:42 -0800 Subject: [PATCH 42/61] proper [ci skip] --- src/library_wasmfs.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index fa5fc722e3569..2fef1453e4aaf 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -196,7 +196,6 @@ var WasmFSLibrary = { // we then connect to the calling C++. _wasmfs_jsimpl_async_alloc_file: function(backend, file, fptr, arg) { -console.log('alloc file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif @@ -208,7 +207,6 @@ console.log('alloc file async', backend); }, _wasmfs_jsimpl_async_free_file: function(backend, file, fptr, arg) { -console.log('free file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif @@ -224,7 +222,6 @@ console.log('free file async', backend); }, _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offsetLow, offsetHigh, fptr, arg) { -console.log('read file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif @@ -238,7 +235,6 @@ console.log('read file async', backend); }, _wasmfs_jsimpl_async_get_size: function(backend, file, fptr, arg) { -console.log('getSize file async', backend); #if ASSERTIONS assert(wasmFS$backends[backend]); #endif From b5b1e33d4428f42391805d006467d26a331f4773 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:07:21 -0800 Subject: [PATCH 43/61] work [ci skip] --- src/library_wasmfs.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 2fef1453e4aaf..690c00b766fda 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -218,7 +218,16 @@ var WasmFSLibrary = { }, _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, offsetLow, offsetHigh, fptr, arg) { - abort('TODO'); +#if ASSERTIONS + assert(wasmFS$backends[backend]); +#endif + {{{ runtimeKeepalivePush() }}} + wasmFS$backends[backend].write(file, buffer, length, offsetLow /* FIXME */).then((size) => { + {{{ runtimeKeepalivePop() }}} + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; + {{{ makeDynCall('vi', 'fptr') }}}(arg); + }); }, _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offsetLow, offsetHigh, fptr, arg) { From 82edc92891d59d5ece4435e0c08634fba9616656 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:19:01 -0800 Subject: [PATCH 44/61] work [ci skip] --- src/library_wasi.js | 5 +---- src/library_wasmfs.js | 10 ++++++---- src/parseTools.js | 11 +++++++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/library_wasi.js b/src/library_wasi.js index a514ba2c40969..8da063ed31366 100644 --- a/src/library_wasi.js +++ b/src/library_wasi.js @@ -270,11 +270,8 @@ var WasiLibrary = { }, fd_seek: function(fd, {{{ defineI64Param('offset') }}}, whence, newOffset) { - {{{ receiveI64ParamAsI32s('offset') }}} + {{{ receiveI64ParamAsDouble('offset') }}} var stream = SYSCALLS.getStreamFromFD(fd); - var HIGH_OFFSET = 0x100000000; // 2^32 - // use an unsigned operator on low and shift high by 32-bits - var offset = offset_high * HIGH_OFFSET + (offset_low >>> 0); var DOUBLE_LIMIT = 0x20000000000000; // 2^53 // we also check for equality since DOUBLE_LIMIT + 1 == DOUBLE_LIMIT diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 690c00b766fda..6014fe44e1452 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -217,12 +217,13 @@ var WasmFSLibrary = { }); }, - _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, offsetLow, offsetHigh, fptr, arg) { + _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}, fptr, arg) { + {{{ receiveI64ParamAsDouble('offset') }}} #if ASSERTIONS assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].write(file, buffer, length, offsetLow /* FIXME */).then((size) => { + wasmFS$backends[backend].write(file, buffer, length, offset).then((size) => { {{{ runtimeKeepalivePop() }}} {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; @@ -230,12 +231,13 @@ var WasmFSLibrary = { }); }, - _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, offsetLow, offsetHigh, fptr, arg) { + _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}, fptr, arg) { + {{{ receiveI64ParamAsDouble('offset') }}} #if ASSERTIONS assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].read(file, buffer, length, offsetLow /* FIXME */).then((size) => { + wasmFS$backends[backend].read(file, buffer, length, offset).then((size) => { {{{ runtimeKeepalivePop() }}} {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; diff --git a/src/parseTools.js b/src/parseTools.js index c4ae1a6c2ba2a..7aef796b5a6b9 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1073,6 +1073,17 @@ function receiveI64ParamAsI32s(name) { return ''; } +function receiveI64ParamAsDouble(name) { + if (WASM_BIGINT) { + // Just convert the bigint into a double. + return `${name} = Number(${name});`; + } + + // Combine the i32 params. Use an unsigned operator on low and shift high by + // 32 bits. + return `${name} = ${name}_high * 0x100000000 + (${name}_low >>> 0);`; +} + function sendI64Argument(low, high) { if (WASM_BIGINT) { return 'BigInt(low) | (BigInt(high) << BigInt(32))'; From 320ad78b01a247f315799f01ed88b678617a4b33 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:19:47 -0800 Subject: [PATCH 45/61] work [ci skip] --- src/library_wasmfs.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 6014fe44e1452..ac3e4b99992ca 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -169,14 +169,16 @@ var WasmFSLibrary = { return wasmFS$backends[backend].freeFile(file); }, - _wasmfs_jsimpl_write: function(backend, file, buffer, length, offset) { + _wasmfs_jsimpl_write: function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}) { + {{{ receiveI64ParamAsDouble('offset') }}} #if ASSERTIONS assert(wasmFS$backends[backend]); #endif return wasmFS$backends[backend].write(file, buffer, length, offset); }, - _wasmfs_jsimpl_read: function(backend, file, buffer, length, offset) { + _wasmfs_jsimpl_read: function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}) { + {{{ receiveI64ParamAsDouble('offset') }}} #if ASSERTIONS assert(wasmFS$backends[backend]); #endif From 0794ed1568e1a52358c2cc584ce7a2ffac8774ec Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:21:18 -0800 Subject: [PATCH 46/61] work [ci skip] --- src/library_wasi.js | 5 ++++- src/parseTools.js | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/library_wasi.js b/src/library_wasi.js index 8da063ed31366..a514ba2c40969 100644 --- a/src/library_wasi.js +++ b/src/library_wasi.js @@ -270,8 +270,11 @@ var WasiLibrary = { }, fd_seek: function(fd, {{{ defineI64Param('offset') }}}, whence, newOffset) { - {{{ receiveI64ParamAsDouble('offset') }}} + {{{ receiveI64ParamAsI32s('offset') }}} var stream = SYSCALLS.getStreamFromFD(fd); + var HIGH_OFFSET = 0x100000000; // 2^32 + // use an unsigned operator on low and shift high by 32-bits + var offset = offset_high * HIGH_OFFSET + (offset_low >>> 0); var DOUBLE_LIMIT = 0x20000000000000; // 2^53 // we also check for equality since DOUBLE_LIMIT + 1 == DOUBLE_LIMIT diff --git a/src/parseTools.js b/src/parseTools.js index 7aef796b5a6b9..f4b7e4942c360 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1073,6 +1073,8 @@ function receiveI64ParamAsI32s(name) { return ''; } +// TODO: use this in library_wasi.js and other places. but we need to add an +// error-handling hook here. function receiveI64ParamAsDouble(name) { if (WASM_BIGINT) { // Just convert the bigint into a double. From ec37827280061f4b3bcd656da52bb62dd84e1cef Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:23:18 -0800 Subject: [PATCH 47/61] work [ci skip] --- tests/wasmfs/wasmfs_jsfile.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/wasmfs/wasmfs_jsfile.c b/tests/wasmfs/wasmfs_jsfile.c index 7a2f3ee65c44f..5e4c1a77bc802 100644 --- a/tests/wasmfs/wasmfs_jsfile.c +++ b/tests/wasmfs/wasmfs_jsfile.c @@ -34,18 +34,13 @@ static backend_t make_js_file_backend(void* arg) { return wasmfs_create_js_file_backend(); } -static backend_t make_backend() { -#ifdef PROXYING - return wasmfs_create_proxied_backend(make_js_file_backend, NULL); -#endif -#ifdef FETCH - return wasmfs_create_fetch_backend("TODO/url"); -#endif - return make_js_file_backend(NULL); -} - int main() { - backend_t backend = make_backend(); + backend_t backend; +#ifndef PROXYING + backend = make_js_file_backend(NULL); +#else + backend = wasmfs_create_proxied_backend(make_js_file_backend, NULL); +#endif // Create a new backend file under root. int fd = wasmfs_create_file("/testfile", 0777, backend); From f2aa924d4faa4b6775a9756ca20b32e531371d52 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:26:52 -0800 Subject: [PATCH 48/61] work [ci skip] --- tests/test_browser.py | 12 ++++++++++-- tests/test_other.py | 11 ----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index 23138943d3737..49bb4ec5d7aa9 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5179,10 +5179,18 @@ def test(args): test(['-sMALLOC=emmalloc-memvalidate']) test(['-sMALLOC=emmalloc-memvalidate-verbose']) - def test_wasmfs_fetch_backend(self): + @parameterized({ + # the fetch backend works even on the main thread: we proxy to a background + # thread and busy-wait + 'main_thread': (['-sPTHREAD_POOL_SIZE=1'],), + # using proxy_to_pthread also works, of course + 'proxy_to_pthread': (['-sPROXY_TO_PTHREAD'],), + }) + @requires_threads + def test_wasmfs_fetch_backend(self, args): create_file('data.dat', 'hello, fetch') self.btest_exit(test_file('wasmfs/wasmfs_fetch.c'), - args=['-sWASMFS', '-sUSE_PTHREADS', '-sPROXY_TO_PTHREAD', '--profiling', '-O0']) + args=['-sWASMFS', '-sUSE_PTHREADS'] + args) @no_firefox('no 4GB support yet') def test_zzz_zzz_emmalloc_memgrowth(self, *args): diff --git a/tests/test_other.py b/tests/test_other.py index d38dc0ff7b346..c3c19938805f7 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -11436,17 +11436,6 @@ def test_wasmfs_jsfile_proxying_backend(self): self.set_setting('EXIT_RUNTIME') self.test_wasmfs_jsfile() - @node_pthreads - def test_wasmfs_fetch_backend(self): - self.emcc_args.append('-DFETCH') - self.emcc_args.append('--profiling') - self.emcc_args.append('-sWASM=0') - self.emcc_args.append('-O2') - self.set_setting('USE_PTHREADS') - self.set_setting('PROXY_TO_PTHREAD') - self.set_setting('EXIT_RUNTIME') - self.test_wasmfs_jsfile() - @disabled('Running with initial >2GB heaps is not currently supported on the CI version of Node') def test_hello_world_above_2gb(self): self.run_process([EMCC, test_file('hello_world.c'), '-sGLOBAL_BASE=2147483648', '-sINITIAL_MEMORY=3GB']) From 5436d7114d786c4a4c117b93a3555edeb6ef0923 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:27:37 -0800 Subject: [PATCH 49/61] work [ci skip] --- tests/browser_reporting.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/browser_reporting.js b/tests/browser_reporting.js index f93ddf83b7561..c96fa7c3112ac 100644 --- a/tests/browser_reporting.js +++ b/tests/browser_reporting.js @@ -20,7 +20,7 @@ function reportResultToServer(result, sync, port) { xhr.send(); if (typeof window === 'object' && window && hasModule && !Module['pageThrewException']) { /* for easy debugging, don't close window on failure */ -// setTimeout(function() { window.close() }, 1000); + setTimeout(function() { window.close() }, 1000); } } } From a9f685b1ac409b329423febacb5799e551542e7e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:28:47 -0800 Subject: [PATCH 50/61] work [ci skip] --- system/lib/wasmfs/proxied_async_js_impl_backend.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 38383429ca59f..a465023be4044 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -23,7 +23,7 @@ using js_index_t = uint32_t; // A callback state that also adds the C++ component for resuming. This is only // needed on the C++ side, but needs to be passed around as part of this -// struct when we go through C and JS> +// struct when we go through C and JS. struct CppCallbackState : public CallbackState { // The function to call to resume execution. emscripten::SyncToAsync::Callback resume; @@ -98,7 +98,6 @@ class ProxiedAsyncJSImplFile : public DataFile { } __wasi_errno_t read(uint8_t* buf, size_t len, off_t offset) override { - // TODO; proxy // The caller should have already checked that the offset + len does // not exceed the file's size. assert(offset + len <= getSize()); From 8ad69f2f7bc55f706b55b0d27501bd003b4a04bd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:32:57 -0800 Subject: [PATCH 51/61] work [ci skip] --- src/library_wasmfs.js | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index ac3e4b99992ca..5af53e18adc79 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -197,43 +197,40 @@ var WasmFSLibrary = { // implementors of backends: the hooks we call should return Promises, which // we then connect to the calling C++. - _wasmfs_jsimpl_async_alloc_file: function(backend, file, fptr, arg) { + _wasmfs_jsimpl_async_alloc_file: async function(backend, file, fptr, arg) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].allocFile(file).then(() => { - {{{ runtimeKeepalivePop() }}} - {{{ makeDynCall('vi', 'fptr') }}}(arg); - }); + await wasmFS$backends[backend].allocFile(file); + {{{ runtimeKeepalivePop() }}} + {{{ makeDynCall('vi', 'fptr') }}}(arg); }, - _wasmfs_jsimpl_async_free_file: function(backend, file, fptr, arg) { + _wasmfs_jsimpl_async_free_file: async function(backend, file, fptr, arg) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].freeFile(file).then(() => { - {{{ runtimeKeepalivePop() }}} - {{{ makeDynCall('vi', 'fptr') }}}(arg); - }); + await wasmFS$backends[backend].freeFile(file); + {{{ runtimeKeepalivePop() }}} + {{{ makeDynCall('vi', 'fptr') }}}(arg); }, - _wasmfs_jsimpl_async_write: function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}, fptr, arg) { + _wasmfs_jsimpl_async_write: async function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}, fptr, arg) { {{{ receiveI64ParamAsDouble('offset') }}} #if ASSERTIONS assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].write(file, buffer, length, offset).then((size) => { - {{{ runtimeKeepalivePop() }}} - {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; - {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; - {{{ makeDynCall('vi', 'fptr') }}}(arg); - }); + var size = await wasmFS$backends[backend].write(file, buffer, length, offset); + {{{ runtimeKeepalivePop() }}} + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; + {{{ makeDynCall('vi', 'fptr') }}}(arg); }, - _wasmfs_jsimpl_async_read: function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}, fptr, arg) { + _wasmfs_jsimpl_async_read: async function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}, fptr, arg) { {{{ receiveI64ParamAsDouble('offset') }}} #if ASSERTIONS assert(wasmFS$backends[backend]); @@ -247,7 +244,7 @@ var WasmFSLibrary = { }); }, - _wasmfs_jsimpl_async_get_size: function(backend, file, fptr, arg) { + _wasmfs_jsimpl_async_get_size: async function(backend, file, fptr, arg) { #if ASSERTIONS assert(wasmFS$backends[backend]); #endif From 721acea80b4f1ce38bed2fd7e6611906ee3c62ec Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:33:08 -0800 Subject: [PATCH 52/61] work [ci skip] --- src/library_wasmfs.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 5af53e18adc79..4581fe1c8d8ca 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -236,12 +236,11 @@ var WasmFSLibrary = { assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].read(file, buffer, length, offset).then((size) => { - {{{ runtimeKeepalivePop() }}} - {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; - {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; - {{{ makeDynCall('vi', 'fptr') }}}(arg); - }); + var size = await wasmFS$backends[backend].read(file, buffer, length, offset); + {{{ runtimeKeepalivePop() }}} + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; + {{{ makeDynCall('vi', 'fptr') }}}(arg); }, _wasmfs_jsimpl_async_get_size: async function(backend, file, fptr, arg) { @@ -249,12 +248,11 @@ var WasmFSLibrary = { assert(wasmFS$backends[backend]); #endif {{{ runtimeKeepalivePush() }}} - wasmFS$backends[backend].getSize(file).then((size) => { - {{{ runtimeKeepalivePop() }}} - {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; - {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; - {{{ makeDynCall('vi', 'fptr') }}}(arg); - }); + var size = await wasmFS$backends[backend].getSize(file); + {{{ runtimeKeepalivePop() }}} + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}}; + {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}}; + {{{ makeDynCall('vi', 'fptr') }}}(arg); }, } From 329c1146dc19850b2c78742692c8e7aabffc7df5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:35:38 -0800 Subject: [PATCH 53/61] work [ci skip] --- src/library_wasmfs_fetch.js | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index c428559fdcc15..9d40d1900042d 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -8,10 +8,10 @@ mergeInto(LibraryManager.library, { '$wasmFS$JSMemoryFiles', '_wasmfs_create_js_file_backend_js', ], - _wasmfs_create_fetch_backend_js: function(backend) { + _wasmfs_create_fetch_backend_js: async function(backend) { // Get a promise that fetches the data and stores it in JS memory (if it has // not already been fetched). - function getFile(file) { + async function getFile(file) { if (wasmFS$JSMemoryFiles[file]) { // The data is already here, so nothing to do before we continue on to // the actual read below. @@ -21,14 +21,9 @@ mergeInto(LibraryManager.library, { // This is the first time we want the file's data. // TODO: real URL! var url = 'data.dat'; - return fetch(url).then((response) => { - if (!response['ok']) { - throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; - } - return response['arrayBuffer'](); - }).then((buffer) => { - wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); - }); + var response = await fetch(url); + var buffer = await response['arrayBuffer'](); + wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); } // Start with the normal JSFile operations. This sets @@ -41,29 +36,27 @@ mergeInto(LibraryManager.library, { wasmFS$backends[backend] = { // alloc/free operations are not actually async. Just forward to the // parent class, but we must return a Promise as the caller expects. - allocFile: function(file) { + allocFile: async function(file) { jsFileOps.allocFile(file); return Promise.resolve(); }, - freeFile: function(file) { + freeFile: async function(file) { jsFileOps.freeFile(file); return Promise.resolve(); }, - write: function(file, buffer, length, offset) { + write: async function(file, buffer, length, offset) { abort("TODO: file writing in fetch backend? read-only for now"); }, // read/getSize fetch the data, then forward to the parent class. - read: function(file, buffer, length, offset) { - return getFile(file).then(() => { - return jsFileOps.read(file, buffer, length, offset); - }); + read: async function(file, buffer, length, offset) { + await getFile(file); + return jsFileOps.read(file, buffer, length, offset); }, - getSize: function(file) { - return getFile(file).then(() => { - return jsFileOps.getSize(file); - }); + getSize: async function(file) { + await getFile(file); + return jsFileOps.getSize(file); }, }; }, From 86ec7709c2e071384e051438cf46ea73bae9aa3a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 11:36:24 -0800 Subject: [PATCH 54/61] format --- system/lib/wasmfs/async_callback.h | 3 +-- system/lib/wasmfs/proxied_async_js_impl_backend.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/system/lib/wasmfs/async_callback.h b/system/lib/wasmfs/async_callback.h index 3aed2ac9107ec..d938ea81479a4 100644 --- a/system/lib/wasmfs/async_callback.h +++ b/system/lib/wasmfs/async_callback.h @@ -9,8 +9,8 @@ #pragma once -#include "wasi/api.h" #include "sys/types.h" +#include "wasi/api.h" // Callbacks for the async API between C and JS. This is declared in a small // separate header for convenience of gen_struct_info. @@ -25,4 +25,3 @@ struct CallbackState { // Some syscalls return an offset. off_t offset; }; - diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index a465023be4044..3b80ea0a0d61a 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -9,10 +9,10 @@ #pragma once +#include "async_callback.h" #include "backend.h" #include "thread_utils.h" #include "wasmfs.h" -#include "async_callback.h" // // Similar to JSImplBackend, but proxies to another thread where the JS can be From 771ad7708707b88bb45da5914bd44c96b8d9fe75 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 15:38:19 -0800 Subject: [PATCH 55/61] refactor to avoid creating a FetchBackend class that creates its own proxy --- system/lib/wasmfs/fetch_backend.cpp | 21 +++---------------- .../wasmfs/proxied_async_js_impl_backend.h | 18 ++++++++++++++++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/system/lib/wasmfs/fetch_backend.cpp b/system/lib/wasmfs/fetch_backend.cpp index 72f15c51030ed..63fcce88963e4 100644 --- a/system/lib/wasmfs/fetch_backend.cpp +++ b/system/lib/wasmfs/fetch_backend.cpp @@ -18,26 +18,11 @@ void _wasmfs_create_fetch_backend_js(wasmfs::backend_t); namespace wasmfs { -class FetchBackend : public Backend { - emscripten::SyncToAsync proxy; - -public: - FetchBackend() { - // Run the JS on the target thread. - proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { - _wasmfs_create_fetch_backend_js(this); - (*resume)(); - }); - } - - std::shared_ptr createFile(mode_t mode) override { - return std::make_shared(mode, this, proxy); - } -}; - extern "C" backend_t wasmfs_create_fetch_backend(char* base_url) { // TODO: use base url, cache on JS side - return wasmFS.addBackend(std::make_unique()); + return wasmFS.addBackend(std::make_unique([](backend_t backend) { + _wasmfs_create_fetch_backend_js(backend); + })); } } // namespace wasmfs diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 3b80ea0a0d61a..c51a6154b965a 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -171,4 +171,22 @@ class ProxiedAsyncJSImplFile : public DataFile { } }; +class ProxiedAsyncJSBackend : public Backend { + emscripten::SyncToAsync proxy; + +public: + // Receives as a parameter a function to call on the proxied thread, which is + // useful for doing setup there. + ProxiedAsyncJSBackend(std::function setupOnThread) { + proxy.invoke([&](emscripten::SyncToAsync::Callback resume) { + setupOnThread(this); + (*resume)(); + }); + } + + std::shared_ptr createFile(mode_t mode) override { + return std::make_shared(mode, this, proxy); + } +}; + } // namespace wasmfs From 621813a32361be31bd7f3ddf51ec0c32c92738c5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 15:38:31 -0800 Subject: [PATCH 56/61] format --- system/lib/wasmfs/fetch_backend.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/system/lib/wasmfs/fetch_backend.cpp b/system/lib/wasmfs/fetch_backend.cpp index 63fcce88963e4..4f11d60031005 100644 --- a/system/lib/wasmfs/fetch_backend.cpp +++ b/system/lib/wasmfs/fetch_backend.cpp @@ -20,9 +20,8 @@ namespace wasmfs { extern "C" backend_t wasmfs_create_fetch_backend(char* base_url) { // TODO: use base url, cache on JS side - return wasmFS.addBackend(std::make_unique([](backend_t backend) { - _wasmfs_create_fetch_backend_js(backend); - })); + return wasmFS.addBackend(std::make_unique( + [](backend_t backend) { _wasmfs_create_fetch_backend_js(backend); })); } } // namespace wasmfs From fa649e6948c3d5747d0bf16364c7a7947ff6a753 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Feb 2022 17:21:15 -0800 Subject: [PATCH 57/61] docs --- system/lib/wasmfs/js_impl_backend.h | 8 +++++--- .../lib/wasmfs/proxied_async_js_impl_backend.h | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/system/lib/wasmfs/js_impl_backend.h b/system/lib/wasmfs/js_impl_backend.h index 9e87eda51f9c3..06ea380d6377b 100644 --- a/system/lib/wasmfs/js_impl_backend.h +++ b/system/lib/wasmfs/js_impl_backend.h @@ -28,14 +28,16 @@ // To write a new backend in JS, you basically do the following: // // 1. Add a declaration of the C function to create the backend in the -// "backend creation" section of emscripten/wasmfs.h. +// "backend creation" section of emscripten/wasmfs.h. (One line.) // 2. Add a cpp file for the new backend, and implement the C function from 1, // which should create it on both the C++ (using JSImplBackend) and JS // sides. (By convention, the C function should just call into C++ and JS -// which do the interesting work; the C is just a thin wrapper.) +// which do the interesting work; the C is just a thin wrapper.) (A few +// lines.) // 3. Write a new JS library, and add the implementation of the JS method just // mentioned, which should set up the mapping from the C++ backend object's -// address to the JS code containing the hooks to read and write etc. +// address to the JS code containing the hooks to read and write etc. (99% +// of the work happens here.) // // For a simple example, see js_file_backend.cpp and library_wasmfs_js_file.js // diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index c51a6154b965a..0bb2dc345cf92 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -18,6 +18,24 @@ // Similar to JSImplBackend, but proxies to another thread where the JS can be // async. // +// To write a new async backend in JS, you basically do the following: +// +// 1. Add a declaration of the C function to create the backend in the +// "backend creation" section of emscripten/wasmfs.h. (One line.) +// 2. Add a cpp file for the new backend, and implement the C function from 1, +// which should create a ProxiedAsyncJSBackend while passing it code to +// set up the JS side. (A few lines.) +// 3. Write a new JS library, and add the implementation of the JS method just +// mentioned, which should set up the mapping from the C++ backend object's +// address to the JS code containing the hooks to read and write etc. The +// hooks should each return a JS Promise. (99% of the work happens here.) +// +// This is basically the same as JSImplBackend, except that the code in step #3 +// is async (also, step #2 looks slightly different, but is similarly very +// short). +// +// For a simple example, see fetch_backend.cpp and library_wasmfs_fetch.js. +// using js_index_t = uint32_t; From cacb8c2ecb0d29d70637b4a00ee68ad722363622 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Feb 2022 07:48:27 -0800 Subject: [PATCH 58/61] update test --- tests/reference_struct_info.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/reference_struct_info.json b/tests/reference_struct_info.json index 1593a35c9931e..e1f2dbb66d9bc 100644 --- a/tests/reference_struct_info.json +++ b/tests/reference_struct_info.json @@ -372,6 +372,11 @@ "__WASI_FILETYPE_SYMBOLIC_LINK": 7 }, "structs": { + "CallbackState": { + "__size__": 16, + "offset": 8, + "result": 0 + }, "EmscriptenBatteryEvent": { "__size__": 32, "charging": 24, From cc77ca9aee001d12387e71a1dddd513f9e21b87c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Feb 2022 09:14:39 -0800 Subject: [PATCH 59/61] comment --- src/library_wasmfs.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/library_wasmfs.js b/src/library_wasmfs.js index 4581fe1c8d8ca..3434149f18383 100644 --- a/src/library_wasmfs.js +++ b/src/library_wasmfs.js @@ -197,6 +197,13 @@ var WasmFSLibrary = { // implementors of backends: the hooks we call should return Promises, which // we then connect to the calling C++. + // TODO: arg is void*, which for MEMORY64 will be 64-bit. we need a way to + // declare arg in the function signature here (like defineI64Param, + // but that varies for wasm32/wasm64), and a way to do makeDynCall that + // adds a 'p' signature type for pointer, or something like that + // (however, dyncalls might also just work, given in MEMORY64 we assume + // WASM_BIGINT so the pointer is just a single argument, just like in + // wasm32). _wasmfs_jsimpl_async_alloc_file: async function(backend, file, fptr, arg) { #if ASSERTIONS assert(wasmFS$backends[backend]); From 2b031d7b9c068e0424a077c9ff5ecfc9a3b3b84f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Feb 2022 09:24:11 -0800 Subject: [PATCH 60/61] indent --- system/lib/wasmfs/proxied_async_js_impl_backend.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/lib/wasmfs/proxied_async_js_impl_backend.h b/system/lib/wasmfs/proxied_async_js_impl_backend.h index 0bb2dc345cf92..b1ea0fc44c909 100644 --- a/system/lib/wasmfs/proxied_async_js_impl_backend.h +++ b/system/lib/wasmfs/proxied_async_js_impl_backend.h @@ -25,10 +25,10 @@ // 2. Add a cpp file for the new backend, and implement the C function from 1, // which should create a ProxiedAsyncJSBackend while passing it code to // set up the JS side. (A few lines.) -// 3. Write a new JS library, and add the implementation of the JS method just -// mentioned, which should set up the mapping from the C++ backend object's -// address to the JS code containing the hooks to read and write etc. The -// hooks should each return a JS Promise. (99% of the work happens here.) +// 3. Write a new JS library, and add the implementation of the JS method just +// mentioned, which should set up the mapping from the C++ backend object's +// address to the JS code containing the hooks to read and write etc. The +// hooks should each return a JS Promise. (99% of the work happens here.) // // This is basically the same as JSImplBackend, except that the code in step #3 // is async (also, step #2 looks slightly different, but is similarly very From cd0d2ef1f2b14a8a7f787307a42c990eeb3f1725 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 10 Feb 2022 09:28:41 -0800 Subject: [PATCH 61/61] use arrow functions --- src/library_wasmfs_fetch.js | 10 +++++----- src/library_wasmfs_js_file.js | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/library_wasmfs_fetch.js b/src/library_wasmfs_fetch.js index 9d40d1900042d..00bb821e7461a 100644 --- a/src/library_wasmfs_fetch.js +++ b/src/library_wasmfs_fetch.js @@ -36,25 +36,25 @@ mergeInto(LibraryManager.library, { wasmFS$backends[backend] = { // alloc/free operations are not actually async. Just forward to the // parent class, but we must return a Promise as the caller expects. - allocFile: async function(file) { + allocFile: async (file) => { jsFileOps.allocFile(file); return Promise.resolve(); }, - freeFile: async function(file) { + freeFile: async (file) => { jsFileOps.freeFile(file); return Promise.resolve(); }, - write: async function(file, buffer, length, offset) { + write: async (file, buffer, length, offset) => { abort("TODO: file writing in fetch backend? read-only for now"); }, // read/getSize fetch the data, then forward to the parent class. - read: async function(file, buffer, length, offset) { + read: async (file, buffer, length, offset) => { await getFile(file); return jsFileOps.read(file, buffer, length, offset); }, - getSize: async function(file) { + getSize: async(file) => { await getFile(file); return jsFileOps.getSize(file); }, diff --git a/src/library_wasmfs_js_file.js b/src/library_wasmfs_js_file.js index 46f15c2d20c8a..07fe88a198625 100644 --- a/src/library_wasmfs_js_file.js +++ b/src/library_wasmfs_js_file.js @@ -9,14 +9,14 @@ mergeInto(LibraryManager.library, { ], _wasmfs_create_js_file_backend_js: function(backend) { wasmFS$backends[backend] = { - allocFile: function(file) { + allocFile: (file) => { // Do nothing: we allocate the typed array lazily, see write() }, - freeFile: function(file) { + freeFile: (file) => { // Release the memory, as it now has no references to it any more. wasmFS$JSMemoryFiles[file] = undefined; }, - write: function(file, buffer, length, offset) { + write: (file, buffer, length, offset) => { try { if (!wasmFS$JSMemoryFiles[file]) { // Initialize typed array on first write operation. @@ -37,7 +37,7 @@ mergeInto(LibraryManager.library, { return {{{ cDefine('EIO') }}}; } }, - read: function(file, buffer, length, offset) { + read: (file, buffer, length, offset) => { try { HEAPU8.set(wasmFS$JSMemoryFiles[file].subarray(offset, offset + length), buffer); return 0; @@ -45,7 +45,7 @@ mergeInto(LibraryManager.library, { return {{{ cDefine('EIO') }}}; } }, - getSize: function(file) { + getSize: (file) => { return wasmFS$JSMemoryFiles[file] ? wasmFS$JSMemoryFiles[file].length : 0; }, };