diff --git a/emcc.py b/emcc.py
index 423646ba05a0c..30db48062364a 100755
--- a/emcc.py
+++ b/emcc.py
@@ -2071,6 +2071,9 @@ def default_setting(name, new_default):
# set location of worker.js
settings.PTHREAD_WORKER_FILE = unsuffixed_basename(target) + '.worker.js'
+
+ # set location of nativefs_worker.js
+ settings.NATIVE_FS_WORKER_FILE = unsuffixed_basename(target) + '.nativefs_worker.js'
else:
settings.JS_LIBRARIES.append((0, 'library_pthread_stub.js'))
@@ -2840,6 +2843,19 @@ def phase_final_emitting(options, state, target, wasm_target, memfile):
minified_worker = building.acorn_optimizer(worker_output, ['minifyWhitespace'], return_output=True)
write_file(worker_output, minified_worker)
+ if settings.EMSCRIPTEN_NATIVE_FS:
+ if not settings.USE_PTHREADS:
+ exit_with_error('EMSCRIPTEN_NATIVE_FS feature needs -s USE_PTHREADS')
+ target_dir = os.path.dirname(os.path.abspath(target))
+ worker_output = os.path.join(target_dir, settings.NATIVE_FS_WORKER_FILE)
+ contents = shared.read_and_preprocess(utils.path_from_root('src/nativefs_worker.js'), expand_macros=True)
+ write_file(worker_output, contents)
+
+ # Minify the worker.js file in optimized builds
+ if (settings.OPT_LEVEL >= 1 or settings.SHRINK_LEVEL >= 1) and not settings.DEBUG_LEVEL:
+ minified_worker = building.acorn_optimizer(worker_output, ['minifyWhitespace'], return_output=True)
+ write_file(worker_output, minified_worker)
+
# track files that will need native eols
generated_text_files_with_native_eols = []
diff --git a/src/library.js b/src/library.js
index 3e39c15b864d2..1168f96aaf82b 100644
--- a/src/library.js
+++ b/src/library.js
@@ -3021,7 +3021,14 @@ LibraryManager.library = {
#if RELOCATABLE
code -= {{{ GLOBAL_BASE }}};
#endif
- var args = readAsmConstArgs(sigPtr, argbuf);
+#if EMSCRIPTEN_NATIVE_FS
+ if ( code === - 1 ) {
+ var args = 0;
+ }
+ else {
+ var args = readAsmConstArgs(sigPtr, argbuf);
+ }
+#endif
#if USE_PTHREADS
if (ENVIRONMENT_IS_PTHREAD) {
// EM_ASM functions are variadic, receiving the actual arguments as a buffer
diff --git a/src/library_nativefs.js b/src/library_nativefs.js
new file mode 100644
index 0000000000000..3a8b394112b3d
--- /dev/null
+++ b/src/library_nativefs.js
@@ -0,0 +1,368 @@
+var LibraryNATIVEFS = {
+ /*
+ Documentation of functions is in include/emscripten/nativefs.h
+ */
+ $NATIVEFS: {
+ /*
+ nativefs_worker: a worker spawned by the runtime, that will
+ allow synchronous reads on files selected by the user.
+
+ spawned in preamble.js, then the runtime will wait for
+ the init_nativefs message to remove the runtimeDependency.
+ */
+ worker: 0,
+ /*
+ Allocated memory region mapped like this :
+ {
+ fs_lock: int,
+ dispatch_index: int,
+ dispatch_param1: int,
+ dispatch_param2: int,
+ dispatch_param3: int,
+ // struct per file:
+ {
+ fileIndex: int, // Starts at one
+ fileLock: int, // protects from concurrent read/seek (same file)
+ readSize: int, // new read size
+ seekOffset: int // new offset size
+ }
+ }
+ */
+ handle: 0,
+ /*
+ Keep track of files in the fs
+ nativefs_init will push to this array each time a file is selected.
+ { fd: , fileName: , fileSize: }
+ */
+ files: [],
+ /*
+ dispatcher:
+ using shared memory to set the index of the function that will be called
+
+ Negative codes are for EM_ASM, and positive codes are
+ for proxiedFunctionTable indexes. If the code is 0, the
+ function will be null and we will be able to handle this special
+ case. (in src/library_pthread.js).
+ sigPtr and arg are ignored.
+ */
+ MAIN_THREAD_CONSTS: [
+ /* dispatcher */
+ function () {
+ var view = new Int32Array(Module.wasmMemory.buffer, NATIVEFS.handle, 5);
+ // get dispatcher index
+ var index = Atomics.load(view, 1);
+ // TODO: catch invalid indexes
+ // when this function fails, it is rarely
+ // with a nice 0 index (value is garbage)
+ if (index == 0) {
+ console.error("nativefs error: could not dispatch functionindex is : " + index );
+ return;
+ }
+ NATIVEFS.MAIN_THREAD_CONSTS[index]();
+ },
+ function () {
+ var view = new Int32Array(Module.wasmMemory.buffer, NATIVEFS.handle, 5);
+ // FIXME: reassign or remove?
+ },
+ /* nativefs_read */
+ function () {
+ var view = new Int32Array(Module.wasmMemory.buffer, NATIVEFS.handle, 5);
+ NATIVEFS.worker.postMessage({
+ ops: "read",
+ addr: Atomics.load(view, 2),
+ handle: Atomics.load(view, 3),
+ size: Atomics.load(view, 4)
+ });
+ },
+ /* nativefs_seek */
+ function () {
+ var view = new Int32Array(Module.wasmMemory.buffer, NATIVEFS.handle, 5);
+ NATIVEFS.worker.postMessage({
+ ops: "seek",
+ handle: Atomics.load(view, 2),
+ offset: Atomics.load(view, 3),
+ whence: Atomics.load(view, 4)
+ });
+ },
+ /* get file size */
+ function() {
+ var view = new Int32Array(Module.wasmMemory.buffer, NATIVEFS.handle, 5);
+ var handle = Atomics.load(view, 2);
+ Atomics.store(view, 3, NATIVEFS.files[handle - 3].fileSize);
+ },
+ /* close file */
+ function() {
+ var view = new Int32Array(Module.wasmMemory.buffer, NATIVEFS.handle, 5);
+ var fd = Atomics.load(view, 2);
+ // remove file from files entry
+ NATIVEFS.files[fd - 3] = {};
+ // remove from files in the worker
+ NATIVEFS.worker.postMessage({ ops: "close_file", fd: fd });
+ Atomics.store(view, 3, 1);
+ }
+ ],
+ },
+ /*
+ If we want to break free from the emscripten demo page, we will need
+ to attach the event listener that connects the file with NATIVEFS to a
+ specific element.
+ */
+ nativefs_init__deps: ['$findEventTarget'],
+ nativefs_init: function(element) {
+ if (ENVIRONMENT_IS_PTHREAD) {
+ err("nativefs: cannot init nativefs in a pthread!");
+ return -1;
+ }
+ let fileHandle;
+ let fileCount = 0;
+ element = document.getElementById("NATIVEFS_button");
+ if (!element) {
+ element = findEventTarget(element);
+ // FIXME: what happens if findEventTarget does not work?
+ }
+ element.addEventListener('click', async function() {
+ element.innerHTML = "Selecting File";
+ // TODO: handle compat mode for firefox
+ // showOpenFilePicker() is a chrome/safari only api for now
+ [fileHandle] = await window.showOpenFilePicker();
+ fileHandle.getFile().then((blob) => {
+ // Send the file to the nativefs_worker
+ NATIVEFS.worker.postMessage( {
+ ops: "push_file",
+ file: blob,
+ });
+ // keep the record in the main thread
+ // (the same will be done in the worker)
+ NATIVEFS.files.push({
+ fileName: blob.name,
+ fileSize: blob.size
+ });
+ fileCount += 1;
+ element.innerHTML = "Opened "+ fileCount + " files";
+ });
+ });
+ return 0;
+ },
+ /*
+ FIXME: helper function to get a value from the main thread,
+ via shared memory
+ */
+ nativefs_get_prop_from_main_thread__deps: ['$mainThreadEM_ASM'],
+ nativefs_get_prop_from_main_thread: function() {
+ //FIXME: not implemented, not sure I need it anymore
+ if (!ENVIRONMENT_IS_PTHREAD) {
+ err("nativefs: operation not available in the main thread!");
+ return -1;
+ }
+ var nativefs_handle = Module.PThread.nativeFSHandle;
+ var view = new Int32Array(Module.wasmMemory.buffer, nativefs_handle, 5);
+ Atomics.store(view,1, 1);
+ mainThreadEM_ASM(-1, 0, 0, 1);
+ },
+ nativefs_read__deps: ['$mainThreadEM_ASM'],
+ nativefs_read: function(fd, buffer, size) {
+ if (!ENVIRONMENT_IS_PTHREAD) {
+ err("nativefs: operation not available in the main thread!");
+ return -1;
+ }
+ if ((fd == -1) || (buffer == 0) || (fd > {{{ NATIVEFS_MAX_FD }}}))
+ return -1;
+ if (size == 0)
+ return 0;
+
+ var nativefs_handle = Module.PThread.nativeFSHandle;
+ var view = new Int32Array(Module.wasmMemory.buffer, nativefs_handle, 5);
+ Atomics.store(view, 1, 4);
+ Atomics.store(view, 2, fd);
+ mainThreadEM_ASM(-1, 0, 0, 1);
+ var fileSize = Atomics.load(view, 3);
+
+ if (size > fileSize)
+ size = fileSize;
+
+ var index = fd - 2;
+ var file_state = new Int32Array(Module.wasmMemory.buffer, Module.PThread.nativeFSHandle + (5*4) + (index*16), 4);
+ // check if the file was pushed
+ Atomics.wait(file_state, 0, 0);
+
+ // Init read size: (don't touch read_size if the lock is still acquired)
+ Atomics.wait(file_state, 1, 1);
+ Atomics.store(file_state, 2, -1);
+
+ // Acquire lock: (unblock atomic.wait() in read())
+ Atomics.store(file_state, 1, 1);
+ Atomics.notify(file_state, 1);
+
+ // setup read call in mainThread
+ Atomics.store(view, 1, 2);
+ // set read ops parameters
+ Atomics.store(view, 2, buffer);
+ Atomics.store(view, 3, fd);
+ Atomics.store(view, 4, size);
+
+ mainThreadEM_ASM(-1, 0, 0, 1);
+
+ // Wait for the size notification
+ Atomics.wait(file_state, 2, -1);
+ // release the lock:
+ Atomics.wait(file_state, 1, 1);
+
+ // return number of bytes read
+ return Atomics.load(file_state, 2);
+ },
+ nativefs_seek: function(fd, offset, whence) {
+ if (!ENVIRONMENT_IS_PTHREAD) {
+ err("nativefs: operation not available in the main thread!");
+ return -1;
+ }
+ if ((fd < 3) || (fd > {{{ NATIVEFS_MAX_FD }}}))
+ return -1;
+ var nativefs_handle = Module.PThread.nativeFSHandle;
+ var view = new Int32Array(Module.wasmMemory.buffer, nativefs_handle, 5);
+ var index = fd - 2;
+ var file_state = new Int32Array(Module.wasmMemory.buffer, Module.PThread.nativeFSHandle + (5*4) + (index*16), 4);
+
+ /*
+ Setup get_filesize call in mainThread
+ it will set the fileSize in the sharedMemory
+ to allow us to read it in Atomics.load() call below
+ */
+ Atomics.store(view, 1, 4);
+ Atomics.store(view, 2, fd);
+ mainThreadEM_ASM(-1, 0, 0, 1);
+
+ var fileSize = Atomics.load(view, 3);
+
+ var currentOffset = Atomics.load(file_state, 3);
+ if ( whence === {{{ cDefine('NATIVEFS_SEEK_SET') }}} ) {
+ if ((offset < 0) || (offset > fileSize)) {
+ return -1;
+ }
+ }
+ else if ( whence === {{{ cDefine('NATIVEFS_SEEK_CUR') }}} ) {
+ if ((currentOffset + offset < 0) || (currentOffset + offset > fileSize)) {
+ return -1;
+ }
+ }
+ else if ( whence === {{{ cDefine('NATIVEFS_SEEK_END') }}} ) {
+ if ((fileSize + offset < 0) || (fileSize + offset > fileSize)) {
+ return -1;
+ }
+ }
+ else {
+ return -1;
+ }
+
+ // check if the file was pushed
+ Atomics.wait(file_state, 0, 0);
+
+ // Acquire lock: (unblock atomic.wait() in seek())
+ Atomics.store(file_state, 1, 1);
+ Atomics.notify(file_state, 1);
+
+ // setup seek call in mainThread
+ Atomics.store(view, 1, 3);
+ // set seek ops parameters
+ Atomics.store(view,2, fd);
+ Atomics.store(view,3, offset);
+ Atomics.store(view,4, whence);
+
+ mainThreadEM_ASM(-1, 0, 0, 1);
+
+ // release the lock here
+ Atomics.wait(file_state, 1, 1);
+ if (file_state[1] == -1) {
+ // the call failed
+ return -1;
+ }
+ // return the new file offset
+ return file_state[3];
+ },
+ nativefs_get_fd_from_filename: function(encodedName) {
+ if (ENVIRONMENT_IS_PTHREAD) {
+ err("nativefs: operation not available in the pthread!");
+ return -1;
+ }
+ if (!encodedName)
+ return -1;
+ var textdecoder = new TextDecoder();
+ var filename_view = textdecoder.decode(encodedName);
+ var flag = 0;
+ var j = 0;
+ for (file in NATIVEFS.files) {
+ var i = 0;
+ for (let c of filename_view.entries()) {
+ // item is [index, value]
+ if (item[1] !== file.fileName[i++]) {
+ flag = 1;
+ break;
+ }
+ }
+ // reached here without setting the flag to 1
+ if (flag === 0)
+ return (j + 3);
+ j++;
+ }
+ // reached end without setting the flag to 1
+ return -1;
+ },
+ /*
+ helper function to mark the specified files item for gc.
+ */
+ nativefs_close_file: function(fd) {
+ if (!ENVIRONMENT_IS_PTHREAD) {
+ err("nativefs: operation not available in the main thread!");
+ return -1;
+ }
+ if ((fd < 3) || (fd > {{{ NATIVEFS_MAX_FD }}}))
+ return -1;
+ var nativefs_handle = Module.PThread.nativeFSHandle;
+ var view = new Int32Array(Module.wasmMemory.buffer, nativefs_handle, 5);
+ /* Setup close_file call in the main Thread */
+ Atomics.store(view, 1, 5);
+ Atomics.store(view, 2, fd);
+ mainThreadEM_ASM(-1, 0, 0, 1);
+
+ // reset file state to 0
+ var index = fd - 2;
+ var file_state = new Int32Array(Module.wasmMemory.buffer, Module.PThread.nativeFSHandle + (5*4) + (index*16), 4);
+ Atomics.store(file_state, 0, 0);
+ Atomics.store(file_state, 1, 0);
+ Atomics.store(file_state, 2, -1);
+ Atomics.store(file_state, 3, -1);
+
+ // return success/error
+ var ret = Atomics.load(view, 3);
+ if (ret == 1)
+ return 0;
+ else
+ return -1;
+ },
+ /*
+ This function should be called when EXIT_RUNTIME is marked in effect.
+ It will invalidate the nativefs handle in the pthreads.
+ No function can be called after that.
+ */
+ nativefs_cleanup: function() {
+ if (ENVIRONMENT_IS_PTHREAD) {
+ err("nativefs: operation not available in a pthread!");
+ return -1;
+ }
+ if ((!NATIVEFS.handle) || (!NATIVEFS.worker)) {
+ return -1;
+ }
+ if (NATIVEFS.handle) {
+ Module._free(NATIVEFS.handle);
+ delete NATIVEFS.files;
+ delete NATIVEFS.worker;
+ delete NATIVEFS.handle;
+ }
+ return 0;
+ },
+}
+
+autoAddDeps(LibraryNATIVEFS, '$JSEvents');
+mergeInto(LibraryManager.library, LibraryNATIVEFS);
+if (EMSCRIPTEN_NATIVE_FS) {
+ DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('$NATIVEFS');
+}
diff --git a/src/library_pthread.js b/src/library_pthread.js
index 561c654eb0e1f..9dc3c5d0feeb4 100644
--- a/src/library_pthread.js
+++ b/src/library_pthread.js
@@ -26,6 +26,9 @@ var LibraryPThread = {
// Contains all Workers that are currently hosting an active pthread.
runningWorkers: [],
tlsInitFunctions: [],
+#if EMSCRIPTEN_NATIVE_FS
+ nativeFSHandle: 0,
+#endif
#if PTHREADS_DEBUG
nextWorkerID: 1,
debugInit: function() {
@@ -69,6 +72,10 @@ var LibraryPThread = {
// things.
PThread['receiveObjectTransfer'] = PThread.receiveObjectTransfer;
PThread['threadInit'] = PThread.threadInit;
+#if EMSCRIPTEN_NATIVE_FS
+ PThread['initNativeFSHandle'] = PThread.initNativeFSHandle;
+ PThread['nativeFSHandle'] = PThread.nativeFSHandle;
+#endif
#if !MINIMAL_RUNTIME
PThread['setExitStatus'] = PThread.setExitStatus;
#endif
@@ -252,6 +259,11 @@ var LibraryPThread = {
killThread(d['thread']);
} else if (cmd === 'cancelThread') {
cancelThread(d['thread']);
+ } else if (cmd === 'initNativeFSHandle') {
+ var sab_view = new Int32Array(d.flag);
+ Atomics.store(sab_view, 1, NATIVEFS.handle);
+ Atomics.store(sab_view, 0, 1);
+ Atomics.notify(sab_view, 0);
} else if (cmd === 'loaded') {
worker.loaded = true;
if (onFinishedLoading) onFinishedLoading(worker);
@@ -375,7 +387,7 @@ var LibraryPThread = {
);
PThread.unusedWorkers.push(new Worker(p.createScriptURL('ignored')));
} else
- #endif
+#endif
PThread.unusedWorkers.push(new Worker(new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url)));
return;
}
@@ -395,9 +407,9 @@ var LibraryPThread = {
PThread.unusedWorkers.push(new Worker(p.createScriptURL('ignored')));
} else
#endif
- PThread.unusedWorkers.push(new Worker(pthreadMainJs));
- },
-
+ PThread.unusedWorkers.push(new Worker(pthreadMainJs));
+ }
+ ,
getNewWorker: function() {
if (PThread.unusedWorkers.length == 0) {
#if !PROXY_TO_PTHREAD && PTHREAD_POOL_SIZE_STRICT
@@ -421,6 +433,19 @@ var LibraryPThread = {
}
return PThread.unusedWorkers.pop();
}
+#if EMSCRIPTEN_NATIVE_FS
+ ,
+ initNativeFSHandle: function() {
+ assert(ENVIRONMENT_IS_PTHREAD);
+ var sab = new SharedArrayBuffer(8);
+ var sab_view = new Int32Array(sab);
+ sab_view[0] = 0; // flag
+ sab_view[1] = 0; // nativefs pointer
+ postMessage( { 'cmd': 'initNativeFSHandle', flag: sab });
+ Atomics.wait(sab_view, 0, 0);
+ return Atomics.load(sab_view, 1);
+ }
+#endif
},
$ptrToString: function(ptr) {
@@ -571,7 +596,18 @@ var LibraryPThread = {
msg.moduleCanvasId = threadParams.moduleCanvasId;
msg.offscreenCanvases = threadParams.offscreenCanvases;
#endif
- worker.runPthread = () => {
+#if EMSCRIPTEN_NATIVE_FS
+ if (NATIVEFS.handle == 0) {
+ // don't change the pointer each time a new thread is spawned
+ NATIVEFS.handle = Module._malloc(16 * 1024 + 5 * 4);
+ NATIVEFS.worker.postMessage({
+ ops: "init_nativefs_runtime",
+ moduleWasmMemory: Module.wasmMemory.buffer,
+ handle: NATIVEFS.handle
+ });
+ }
+#endif
+ worker.runPthread = () => {
// Ask the worker to start executing its pthread entry point function.
msg.time = performance.now();
worker.postMessage(msg, threadParams.transferList);
@@ -920,6 +956,14 @@ var LibraryPThread = {
'emscripten_proxy_to_main_thread_js',
'emscripten_receive_on_main_thread_js_callArgs'],
emscripten_receive_on_main_thread_js: function(index, numCallArgs, args) {
+#if EMSCRIPTEN_NATIVE_FS
+ if (index === 0) {
+ func = NATIVEFS.MAIN_THREAD_CONSTS[0];
+ // unused in this case
+ numCallArgs = 0;
+ return func.apply(null);
+ }
+#endif
#if WASM_BIGINT
numCallArgs /= 2;
#endif
@@ -1005,6 +1049,16 @@ var LibraryPThread = {
// in sync and might not contain the function pointer `ptr` at all.
__emscripten_thread_sync_code();
#endif
+#if EMSCRIPTEN_NATIVE_FS
+ if (NATIVEFS.handle === undefined) {
+ /*
+ you don't want the pthread worker to start before
+ the native_fs worker started.
+ */
+ var view = new Int32Array(Module.wasmMemory.buffer, NATIVEFS.handle, 3);
+ Atomics.wait(view, 0, 0);
+ }
+#endif
return {{{ makeDynCall('ii', 'ptr') }}}(arg);
},
diff --git a/src/modules.js b/src/modules.js
index 540c2b923873e..2bd170a6cd4f4 100644
--- a/src/modules.js
+++ b/src/modules.js
@@ -131,6 +131,10 @@ global.LibraryManager = {
libraries.push('library_lz4.js');
}
+ if (EMSCRIPTEN_NATIVE_FS) {
+ libraries.push('library_nativefs.js')
+ }
+
if (MAX_WEBGL_VERSION >= 2) {
// library_webgl2.js must be included only after library_webgl.js, so if we are
// about to include library_webgl2.js, first squeeze in library_webgl.js.
diff --git a/src/nativefs_worker.js b/src/nativefs_worker.js
new file mode 100644
index 0000000000000..66a0fb1598152
--- /dev/null
+++ b/src/nativefs_worker.js
@@ -0,0 +1,146 @@
+var files = [];
+var fd = 2;
+var sharedMemory;
+var fs_lock = -1;
+var nativefs_handle;
+
+this.onmessage = function (msg) {
+ if (msg.data.ops === "push_file") {
+ if (msg.data.file === undefined) {
+ console.error("invalid operation: called push_file operation with invalid" +
+ " file: " + msg.data.file + " } !");
+ return ;
+ }
+ fd += 1;
+ files.push({
+ handle: fd,
+ offset: 0,
+ file: msg.data.file
+ });
+
+ var index = fd - 2;
+ var file_state = new Int32Array(sharedMemory, nativefs_handle + (5*4) + (index*16), 4);
+
+ Atomics.store(file_state, 1, 0);
+ Atomics.notify(file_state, 1);
+ Atomics.store(file_state, 0, fd);
+ Atomics.notify(file_state, 0);
+ }
+ else if (msg.data.ops === "close_file") {
+ if (msg.data.fd === undefined) {
+ console.error("invalid operation: called close_file operation with invalid" +
+ " fd: " + msg.data.fd + " } !");
+ return ;
+ }
+
+ }
+ else if (msg.data.ops === "close_file") {
+ if (msg.data.fs === undefined) {
+ console.error("invalid operation: called close_file operation with invalid" +
+ " fd : " + msg.data.fd + " } !");
+ return ;
+ }
+ files[fd - 3].handle = 0;
+ files[fd - 3].offset = 0;
+ files[fd - 3].file = 0;
+ }
+ /* not implemented, only for debug */
+ else if (msg.data.ops === "cleanup_nativefs") {
+ for (let file of files)
+ console.log(file);
+ files = [];
+ }
+ else if (msg.data.ops === "init_nativefs") {
+ /*
+ worker is ready to receive the runtime
+ */
+ this.postMessage( { ops: "init_nativefs" } );
+ }
+ else if (msg.data.ops === "init_nativefs_runtime") {
+ if (msg.data.moduleWasmMemory === undefined) {
+ console.error("invalid operation: called init_nativefs_runtime operation with invalid" +
+ " { moduleWasmMemory: " +
+ msg.data.moduleWasmMemory +
+ " nativefs_handle: " +
+ msg.data.handle + " } !");
+ return ;
+ }
+ sharedMemory = msg.data.moduleWasmMemory;
+ nativefs_handle = msg.data.handle;
+ this.postMessage( { ops: "init_fs_lock" } );
+ }
+ else if (msg.data.ops === "read") {
+ if (msg.data.addr === undefined
+ || msg.data.handle === undefined
+ || msg.data.size === undefined) {
+ console.error("invalid operation: called read operation with invalid { addr: " +
+ msg.data.addr + " handle: " + msg.data.handle +
+ " size: " + msg.data.size + " } !");
+ return ;
+ }
+ let p_buffer = msg.data.addr;
+ let index = msg.data.handle - 3;
+ let size = msg.data.size;
+ let offset = files[index].offset;
+ let file = files[index].file;
+ let handle_index = msg.data.handle - 2;
+
+ var file_state = new Int32Array(sharedMemory, nativefs_handle + (5*4) + (handle_index*16), 4);
+
+ // wait for read() call to acquire lock
+ Atomics.wait(file_state, 1, 0);
+ let blob = new Blob(
+ [file.slice(offset, offset + size)],
+ { type: 'application/octet-stream' });
+ let reader = new FileReaderSync();
+ let result = reader.readAsArrayBuffer(blob);
+ let dataView = new Uint8Array(result);
+ let wasmMemoryView = new Uint8Array(sharedMemory, p_buffer, dataView.length);
+ let wasmMemoryItems = wasmMemoryView.entries();
+ for ( let item of dataView.entries() ) {
+ //item is [index, value]
+ let ind = wasmMemoryItems.next().value[0];
+ wasmMemoryView[ind] = item[1];
+ }
+ files[index].offset += dataView.length;
+ Atomics.store(file_state, 3, files[index].offset);
+ // finished reading:
+ // 1. udpate size
+ Atomics.store(file_state, 2, dataView.length);
+ Atomics.notify(file_state, 2);
+ // 2. notify read()
+ Atomics.store(file_state, 1, 0);
+ Atomics.notify(file_state, 1);
+ }
+ else if (msg.data.ops === "seek") {
+ if (msg.data.offset === undefined
+ || msg.data.whence === undefined
+ || msg.data.handle === undefined) {
+ console.error("invalid operation: called seek operation with invalid { offset: " + msg.data.offset
+ + " whence: " + msg.data.whence
+ + " handle: " + msg.data.handle
+ + " } !");
+ return ;
+ }
+ let handle_index = msg.data.handle - 2;
+ var file_state = new Int32Array(sharedMemory, nativefs_handle + (5*4) + (handle_index*16), 4);
+ let index = msg.data.handle - 3;
+
+ // wait for seek() call to acquire lock
+ Atomics.wait(file_state, 1, 0);
+ let new_offset = msg.data.offset;
+ if (msg.data.whence === {{{ cDefine('NATIVEFS_SEEK_CUR') }}} ) {
+ new_offset = files[index].offset + msg.data.offset;
+ }
+ else if (msg.data.whence === {{{ cDefine('NATIVEFS_SEEK_END') }}} ) {
+ new_offset = files[index].file.size + msg.data.offset;
+ }
+ files[index].offset = new_offset;
+
+ // notify seek()
+ Atomics.store(file_state, 1, 0);
+ Atomics.notify(file_state, 1);
+ Atomics.store(file_state, 3, new_offset);
+ Atomics.notify(file_state, 3);
+ }
+}
diff --git a/src/postamble.js b/src/postamble.js
index 594d2bec55fd2..e9ce341d17234 100644
--- a/src/postamble.js
+++ b/src/postamble.js
@@ -190,6 +190,9 @@ function callMain(args) {
assert(ret == 0, '_emscripten_proxy_main failed to start proxy thread: ' + ret);
#endif
#else
+#if EMSCRIPTEN_NATIVE_FS
+ _nativefs_cleanup();
+#endif
// if we're not running an evented main loop, it's time to exit
exit(ret, /* implicit = */ true);
return ret;
diff --git a/src/preamble.js b/src/preamble.js
index 6cb195fcc1163..8d0a498e5d51e 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -338,7 +338,46 @@ function preRun() {
#if ASSERTIONS && USE_PTHREADS
assert(!ENVIRONMENT_IS_PTHREAD); // PThreads reuse the runtime from the main thread.
#endif
-
+#if EMSCRIPTEN_NATIVE_FS
+#if ENVIRONMENT_MAY_BE_NODE
+ // FIXME: this does not work for now, this should not run in node
+ // err("nativefs error: EMSCRIPTEN_NATIVE_FS is a web-only api!");
+#endif
+ if (NATIVEFS.worker === 0) {
+ /*
+ The function will make sure the runtime worker is set, and
+ will initialise the nativefs structure.
+
+ It must be a runDependency, the main program should not run
+ before nativefs is ready to push/read/seek files.
+ */
+ addRunDependency('nativefs_init');
+ NATIVEFS.worker = new Worker("./" + '{{{ NATIVE_FS_WORKER_FILE }}}');
+ NATIVEFS.handle = 0;
+ NATIVEFS.files = [];
+ /*
+ This message will "round trip", because we have no guarantees
+ the runtime fs worker won't be ready before the runtime.
+ (which would be bad because we would send an empty sharedMemory)
+ */
+ NATIVEFS.worker.postMessage({ ops: "init_nativefs" });
+ NATIVEFS.worker.onmessage = function (msg) {
+ if (msg.data.ops === "init_nativefs") {
+ removeRunDependency('nativefs_init');
+ }
+ else if (msg.data.ops === "init_fs_lock") {
+ /*
+ sharedMemory was successfuly received, we can set the
+ fs lock (all the pthreads must wait on this before they
+ are ready to read/seek files)
+ */
+ var view = new Int32Array(Module.wasmMemory.buffer, NATIVEFS.handle, 5);
+ Atomics.store(view, 0, 1);
+ Atomics.notify(view, 0);
+ }
+ };
+ }
+#endif
#if expectToReceiveOnModule('preRun')
if (Module['preRun']) {
if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']];
diff --git a/src/settings.js b/src/settings.js
index 80354a27b8c8c..fbc70695f02b4 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -1994,6 +1994,16 @@ var OFFSCREEN_FRAMEBUFFER_FORBID_VAO_PATH = 0;
// [link]
var TEST_MEMORY_GROWTH_FAILS = 0;
+// This option will enable exposing the native file system throught
+// the native filesystem api (web.dev/file-system-access).
+// It is desabled by default.
+var EMSCRIPTEN_NATIVE_FS = 0;
+
+// This option depends on the previous one, it controls the size of allocated
+// nativefs structure that contains state information on the files currently open.
+// default value is 1024, maximum number of file descriptors allowed.
+var NATIVEFS_MAX_FD=1024
+
// For renamed settings the format is:
// [OLD_NAME, NEW_NAME]
// For removed settings (which now effectively have a fixed value and can no
diff --git a/src/settings_internal.js b/src/settings_internal.js
index 15544a1643244..ada7240cb172b 100644
--- a/src/settings_internal.js
+++ b/src/settings_internal.js
@@ -129,6 +129,9 @@ var WASM_BINARY_FILE = '';
// name of the file containing the pthread *.worker.js, if relevant
var PTHREAD_WORKER_FILE = '';
+// name of the file containing the filesystem runtime worker
+var NATIVE_FS_WORKER_FILE = '';
+
// Base URL the source mapfile, if relevant
var SOURCE_MAP_BASE = '';
diff --git a/src/shell.html b/src/shell.html
index a927edccd411e..669118633d5be 100644
--- a/src/shell.html
+++ b/src/shell.html
@@ -1215,7 +1215,7 @@
-
+