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 @@ - +