From 7f67200c81d98659c41723d39c063c3d15b3fb87 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 9 Sep 2024 17:50:44 -0700 Subject: [PATCH] Enable SINGLE_FILE + PTHREADS html output In SINGLE_FILE mode, the JS is inlined directly into the generated HTML. However for pthreads builds we need to be able to create workers that use the same inlined JS. To enable this we encode the JS file into a data URL that can be used for the script `src` attribute. The pthread worker can then use this `src`. --- test/test_browser.py | 6 ++++++ tools/link.py | 30 ++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/test/test_browser.py b/test/test_browser.py index abf207e33761e..186bad2d23580 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -3693,11 +3693,17 @@ def test_pthread_gcc_spinlock(self): '': ([],), 'O3': (['-O3'],), 'minimal_runtime': (['-sMINIMAL_RUNTIME'],), + 'single_file': (['-sSINGLE_FILE'],), }) def test_pthread_create(self, args): self.btest_exit('pthread/test_pthread_create.c', args=['-pthread', '-sPTHREAD_POOL_SIZE=8'] + args, extra_tries=0) # this should be 100% deterministic + files = os.listdir('.') + if '-sSINGLE_FILE' in args: + self.assertEqual(len(files), 1, files) + else: + self.assertEqual(len(files), 4, files) # Test that preallocating worker threads work. def test_pthread_preallocates_workers(self): diff --git a/tools/link.py b/tools/link.py index 992939059c111..1a5b6c931fdda 100644 --- a/tools/link.py +++ b/tools/link.py @@ -2079,7 +2079,7 @@ def phase_final_emitting(options, state, target, wasm_target): return target_dir = os.path.dirname(os.path.abspath(target)) - if settings.PTHREADS and not settings.STRICT: + if settings.PTHREADS and not settings.STRICT and not settings.SINGLE_FILE: worker_file = shared.replace_suffix(target, get_worker_js_suffix()) write_file(worker_file, '''\ // This file is no longer used by emscripten and has been created as a placeholder @@ -2535,14 +2535,19 @@ def generate_traditional_runtime_html(target, options, js_target, target_basenam # Normal code generation path script.src = base_js_target - # inline script for SINGLE_FILE output if settings.SINGLE_FILE: - js_contents = script.inline or '' - if script.src: - js_contents += read_file(js_target) + # In SINGLE_FILE mode we either inline the script, or in the case + # of SHARED_MEMORY convert the entire thing into a data URL. + if settings.SHARED_MEMORY: + assert not script.inline + script.src = get_subresource_location(js_target) + else: + js_contents = script.inline or '' + if script.src: + js_contents += read_file(js_target) + script.src = None + script.inline = read_file(js_target) delete_file(js_target) - script.src = None - script.inline = js_contents else: if not settings.WASM_ASYNC_COMPILATION: # We need to load the wasm file before anything else, since it @@ -2819,16 +2824,21 @@ def replacement(self): """Returns the script tag to replace the {{{ SCRIPT }}} tag in the target""" assert (self.src or self.inline) and not (self.src and self.inline) if self.src: - quoted_src = quote(self.src) + src = self.src + if src.startswith('data:'): + filename = src + else: + src = quote(self.src) + filename = f'./{src}' if settings.EXPORT_ES6: return f''' ''' else: - return f'' + return f'' else: return '' % self.inline