Merge branch 'main' into shared-wasmgc-pthread-create
diff --git a/em++.py b/em++.py index 2ab74f0..407180d 100755 --- a/em++.py +++ b/em++.py
@@ -7,9 +7,6 @@ import sys import emcc -from tools import shared - -shared.run_via_emxx = True if __name__ == '__main__': try:
diff --git a/emcc.py b/emcc.py index 6ca8497..9bf2e2d 100644 --- a/emcc.py +++ b/emcc.py
@@ -43,6 +43,7 @@ compile, config, diagnostics, + ports, shared, system_libs, utils, @@ -195,12 +196,98 @@ return output.strip() +def get_clang(): + if shared.run_via_emxx: + return shared.CLANG_CXX + else: + return shared.CLANG_CC + + +def handle_early_exit_flags(args, newargs): + if '--version' in args: + print(cmdline.version_string()) + print('''\ +Copyright (C) 2026 the Emscripten authors (see AUTHORS.txt) +This is free and open source software under the MIT license. +There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +''') + return True + + if '-dumpversion' in args: # gcc's doc states "Print the compiler version [...] and don't do anything else." + print(utils.EMSCRIPTEN_VERSION) + return True + + # Sadly we cannot rely on PASSTHROUGH_FLAGS for -print-search-dirs or -print-libgcc-file-name + # because there is no way to tell clang today about our custom library paths. + # TODO: Teach clang about emscripten's library layout so we can remove this code. + if '-print-search-dirs' in args or '--print-search-dirs' in args: + print(f'programs: ={config.LLVM_ROOT}') + resource_dir = get_clang_resource_dir(args) + libdir = cache.get_lib_dir(absolute=True) + print(f'libraries: ={resource_dir}{os.pathsep}{libdir}') + return True + + if '-print-libgcc-file-name' in args or '--print-libgcc-file-name' in args: + settings.limit_settings(None) + clang_rt = system_libs.Library.get_usable_variations()['libclang_rt.builtins'] + print(clang_rt.get_path(absolute=True)) + return True + + print_file_name = [a for a in args if a.startswith(('-print-file-name=', '--print-file-name='))] + if print_file_name: + libname = print_file_name[-1].split('=')[1] + resource_dir = get_clang_resource_dir(args) + system_libpath = cache.get_lib_dir(absolute=True) + for dirname in (resource_dir, system_libpath): + fullpath = os.path.join(dirname, libname) + if os.path.isfile(fullpath): + print(fullpath) + break + else: + print(libname) + return True + + if any(a in PASSTHROUGH_FLAGS for a in args) or any(a.startswith(p) for p in PASSTHROUGH_PREFIXES for a in args): + # For several -print-xxx-name flags we just defer to clang rather than + # trying to re-implement the logic. + shared.exec_process([get_clang(), *compile.get_cflags(tuple(args)), *newargs]) + assert False, 'exec_process should not return' + + if options.clear_cache: + logger.info('clearing cache as requested by --clear-cache: `%s`', cache.cachedir) + cache.erase() + shared.perform_sanity_checks() # this is a good time for a sanity check + return True + + if options.clear_ports: + logger.info('clearing ports and cache as requested by --clear-ports') + ports.clear() + cache.erase() + shared.perform_sanity_checks() # this is a good time for a sanity check + return True + + if options.check: + print(cmdline.version_string(), file=sys.stderr) + shared.check_sanity(force=True) + return True + + if options.show_ports: + ports.show_ports() + return True + + if '--cflags' in args: + # Just print the flags we pass to clang and exit. We need to do this after + # phase_setup because the setup sets things like SUPPORT_LONGJMP. + cflags = compile.get_cflags(x for x in args if x != '--cflags') + print(shlex.join(cflags)) + return True + + return False + + @ToolchainProfiler.profile() def main(args): - if shared.run_via_emxx: - clang = shared.CLANG_CXX - else: - clang = shared.CLANG_CC + shared.run_via_emxx = os.path.basename(args[0]).startswith('em++') # Special case the handling of `-v` because it has a special/different meaning # when used with no other arguments. In particular, we must handle this early @@ -210,7 +297,7 @@ if len(args) == 2 and args[1] == '-v': # autoconf likes to see 'GNU' in the output to enable shared object support print(cmdline.version_string(), file=sys.stderr) - return shared.check_call([clang, '-v', *compile.get_target_flags()], check=False).returncode + return shared.check_call([get_clang(), '-v', *compile.get_target_flags()], check=False).returncode # Additional compiler flags that we treat as if they were passed to us on the # commandline @@ -255,75 +342,18 @@ if not shared.SKIP_SUBPROCS: shared.check_sanity() + for port in options.use_ports: + ports.handle_use_port_arg(settings, port) + # For internal consistency, ensure we don't attempt to read or write any link time # settings until we reach the linking phase. settings.limit_settings(COMPILE_TIME_SETTINGS) phase_setup(state) - # Begin early-exit flag handling. - - if '--version' in args: - print(cmdline.version_string()) - print('''\ -Copyright (C) 2026 the Emscripten authors (see AUTHORS.txt) -This is free and open source software under the MIT license. -There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -''') + if handle_early_exit_flags(args, newargs): return 0 - if '-dumpversion' in args: # gcc's doc states "Print the compiler version [...] and don't do anything else." - print(utils.EMSCRIPTEN_VERSION) - return 0 - - # Sadly we cannot rely on PASSTHROUGH_FLAGS for -print-search-dirs or -print-libgcc-file-name - # because there is no way to tell clang today about our custom library paths. - # TODO: Teach clang about emscripten's library layout so we can remove this code. - if '-print-search-dirs' in args or '--print-search-dirs' in args: - print(f'programs: ={config.LLVM_ROOT}') - resource_dir = get_clang_resource_dir(args) - libdir = cache.get_lib_dir(absolute=True) - print(f'libraries: ={resource_dir}{os.pathsep}{libdir}') - return 0 - - if '-print-libgcc-file-name' in args or '--print-libgcc-file-name' in args: - settings.limit_settings(None) - clang_rt = system_libs.Library.get_usable_variations()['libclang_rt.builtins'] - print(clang_rt.get_path(absolute=True)) - return 0 - - print_file_name = [a for a in args if a.startswith(('-print-file-name=', '--print-file-name='))] - if print_file_name: - libname = print_file_name[-1].split('=')[1] - resource_dir = get_clang_resource_dir(args) - system_libpath = cache.get_lib_dir(absolute=True) - for dirname in (resource_dir, system_libpath): - fullpath = os.path.join(dirname, libname) - if os.path.isfile(fullpath): - print(fullpath) - break - else: - print(libname) - return 0 - - if 'EMCC_REPRODUCE' in os.environ: - options.reproduce = os.environ['EMCC_REPRODUCE'] - - if any(a in PASSTHROUGH_FLAGS for a in args) or any(a.startswith(p) for p in PASSTHROUGH_PREFIXES for a in args): - # For several -print-xxx-name flags we just defer to clang rather than - # trying to re-implement the logic. - shared.exec_process([clang, *compile.get_cflags(tuple(args)), *newargs]) - assert False, 'exec_process should not return' - - if '--cflags' in args: - # Just print the flags we pass to clang and exit. We need to do this after - # phase_setup because the setup sets things like SUPPORT_LONGJMP. - cflags = compile.get_cflags(x for x in args if x != '--cflags') - print(shlex.join(cflags)) - return 0 - - # End early-exit flag handling - if options.reproduce: create_reproduce_file(options.reproduce, args) @@ -493,10 +523,7 @@ @ToolchainProfiler.profile_block('compile inputs') def phase_compile_inputs(state, newargs): - if shared.run_via_emxx: - compiler = [shared.CLANG_CXX] - else: - compiler = [shared.CLANG_CC] + compiler = [get_clang()] if config.COMPILER_WRAPPER: logger.debug('using compiler wrapper: %s', config.COMPILER_WRAPPER)
diff --git a/src/lib/libbrowser.js b/src/lib/libbrowser.js index 5844baa..7e37cf9 100644 --- a/src/lib/libbrowser.js +++ b/src/lib/libbrowser.js
@@ -259,7 +259,9 @@ if (!Browser.fullscreenHandlersInstalled) { Browser.fullscreenHandlersInstalled = true; document.addEventListener('fullscreenchange', fullscreenChange); +#if MIN_SAFARI_VERSION < 160400 document.addEventListener('webkitfullscreenchange', fullscreenChange); +#endif } // create a new parent to ensure the canvas has no siblings. this allows browsers to optimize full screen performance when its parent is the full screen root
diff --git a/test/emsdk_version.txt b/test/emsdk_version.txt index 787e37c..8dfa358 100644 --- a/test/emsdk_version.txt +++ b/test/emsdk_version.txt
@@ -1 +1 @@ -ded6d7cb437bc9b669358e096340af318db11038 +f121500928924d2a62bae43f4d6f07045fd17993
diff --git a/test/sockets/webrtc_host.c b/test/sockets/webrtc_host.c deleted file mode 100644 index cdbc3ee..0000000 --- a/test/sockets/webrtc_host.c +++ /dev/null
@@ -1,99 +0,0 @@ -/* - * Copyright 2013 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 <errno.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/ioctl.h> -#include <assert.h> -#ifdef __EMSCRIPTEN__ -#include <emscripten.h> -#endif - -#define EXPECTED_BYTES 5 -#define BUFLEN 16 - -int result = 0; -int sock; -char buf[BUFLEN]; -char expected[] = "emscripten"; -struct sockaddr_in si_host, - si_peer; -struct iovec iov[1]; -struct msghdr hdr; -int done = 0; - -void iter() { - int n; - n = recvmsg(sock, &hdr, 0); - - if(0 < n) { - done = 1; - fprintf(stderr, "received %d bytes: %s", n, (char*)hdr.msg_iov[0].iov_base); - - shutdown(sock, SHUT_RDWR); - close(sock); - -#ifdef __EMSCRIPTEN__ - if(strlen((char*)hdr.msg_iov[0].iov_base) == strlen(expected) && - 0 == strncmp((char*)hdr.msg_iov[0].iov_base, expected, strlen(expected))) { - result = 1; - } - REPORT_RESULT(result); - exit(EXIT_SUCCESS); - emscripten_cancel_main_loop(); -#endif - } else if(EWOULDBLOCK != errno) { - perror("recvmsg failed"); - exit(EXIT_FAILURE); - emscripten_cancel_main_loop(); - } -} - -int main(void) -{ - memset(&si_host, 0, sizeof(struct sockaddr_in)); - memset(&si_peer, 0, sizeof(struct sockaddr_in)); - - si_host.sin_family = AF_INET; - si_host.sin_port = htons(8991); - si_host.sin_addr.s_addr = htonl(INADDR_ANY); - - if(-1 == (sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP))) { - perror("cannot create host socket"); - exit(EXIT_FAILURE); - } - - if(-1 == bind(sock, (struct sockaddr*)&si_host, sizeof(struct sockaddr))) { - perror("cannot bind host socket"); - exit(EXIT_FAILURE); - } - - iov[0].iov_base = buf; - iov[0].iov_len = sizeof(buf); - - memset (&hdr, 0, sizeof (struct msghdr)); - - hdr.msg_name = &si_peer; - hdr.msg_namelen = sizeof(struct sockaddr_in); - hdr.msg_iov = iov; - hdr.msg_iovlen = 1; - -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(iter, 0, 0); -#else - while (!done) iter(); -#endif - - return EXIT_SUCCESS; -}
diff --git a/test/sockets/webrtc_peer.c b/test/sockets/webrtc_peer.c deleted file mode 100644 index 0b827d3..0000000 --- a/test/sockets/webrtc_peer.c +++ /dev/null
@@ -1,88 +0,0 @@ -/* - * Copyright 2013 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 <errno.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/ioctl.h> -#include <assert.h> -#ifdef __EMSCRIPTEN__ -#include <emscripten.h> -#endif - -#define EXPECTED_BYTES 5 -#define BUFLEN 16 -#define HOST_ADDR "10.0.0.1" - -int result = 0; -int sock; -char buf[16] = "emscripten"; -struct sockaddr_in si_host; -struct iovec iov[1]; -struct msghdr hdr; -int done = 0; - -void iter() { - int n; - n = sendmsg(sock, &hdr, 0); - - if(0 < n) { - done = 1; - fprintf(stderr, "sent %d bytes: %s", n, (char*)hdr.msg_iov[0].iov_base); - - shutdown(sock, SHUT_RDWR); - close(sock); - - exit(EXIT_SUCCESS); - emscripten_cancel_main_loop(); - } else if(EWOULDBLOCK != errno) { - perror("sendmsg failed"); - exit(EXIT_FAILURE); - emscripten_cancel_main_loop(); - } -} - -int main(void) -{ - memset(&si_host, 0, sizeof(struct sockaddr_in)); - - si_host.sin_family = AF_INET; - si_host.sin_port = htons(8991); - if(0 == inet_pton(AF_INET, HOST_ADDR, &si_host.sin_addr)) { - perror("inet_aton failed"); - exit(EXIT_FAILURE); - } - - if(-1 == (sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP))) { - perror("cannot create socket"); - exit(EXIT_FAILURE); - } - - iov[0].iov_base = buf; - iov[0].iov_len = sizeof(buf); - - memset (&hdr, 0, sizeof (struct msghdr)); - - hdr.msg_name = &si_host; - hdr.msg_namelen = sizeof(struct sockaddr_in); - hdr.msg_iov = iov; - hdr.msg_iovlen = 1; - -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(iter, 0, 0); -#else - while (!done) iter(); -#endif - - return EXIT_SUCCESS; -}
diff --git a/test/test_other.py b/test/test_other.py index 41421b3..92913a0 100644 --- a/test/test_other.py +++ b/test/test_other.py
@@ -14314,10 +14314,9 @@ expected = 'Stack overflow! Stack cookie has been overwritten at 0x[a-f0-9]*, expected hex dwords 0x89bacdfe and 0x02135467, but received 0xaaaaaaa0 0xfffffff0' self.do_runf('test.c', expected, regex=True, cflags=args + ['-sSTACK_OVERFLOW_CHECK=1'], assert_returncode=NON_ZERO) - @crossplatform - def test_reproduce(self): + def do_test_reproduce(self, args): ensure_dir('tmp') - self.run_process([EMCC, '-sASSERTIONS=1', '--reproduce=foo.tar', '-otmp/out.js', test_file('hello_world.c')]) + self.run_process([EMCC, '-sASSERTIONS=1', '-otmp/out.js', test_file('hello_world.c'), *args]) self.assertExists('foo.tar') names = [] root = os.path.splitdrive(path_from_root())[1][1:] @@ -14346,6 +14345,14 @@ response = response.replace(root, '<root>') self.assertTextDataIdentical(expected, response) + @crossplatform + def test_reproduce(self): + self.do_test_reproduce(['--reproduce=foo.tar']) + + @with_env_modify({'EMCC_REPRODUCE': 'foo.tar'}) + def test_reproduce_env(self): + self.do_test_reproduce([]) + def test_min_browser_version(self): expected = 'emcc: error: MIN_SAFARI_VERSION=140100 is not compatible with WASM_BIGINT (MIN_SAFARI_VERSION=150000 or above required)' self.assert_fail([EMCC, test_file('hello_world.c'), '-Werror', '-sWASM_BIGINT', '-sMIN_SAFARI_VERSION=140100'], expected)
diff --git a/tools/cmdline.py b/tools/cmdline.py index e77e5f6..1b6d02b 100644 --- a/tools/cmdline.py +++ b/tools/cmdline.py
@@ -8,7 +8,6 @@ import os import re import shlex -import sys from enum import Enum, auto, unique from subprocess import PIPE @@ -53,6 +52,9 @@ class EmccOptions: + check = False + clear_cache = False + clear_ports = False cpu_profiler = False dash_E = False dash_M = False @@ -94,10 +96,11 @@ pre_js: list[str] = [] # before all js preload_files: list[str] = [] relocatable = False - reproduce = None + reproduce = os.getenv('EMCC_REPRODUCE') # None by default. requested_debug = None sanitize: set[str] = set() sanitize_minimal_runtime = False + show_ports = False s_args: list[str] = [] save_temps = False shared = False @@ -106,6 +109,7 @@ syntax_only = False target = '' use_closure_compiler = None + use_ports: list[str] = [] use_preload_cache = False use_preload_plugins = False valid_abspaths: list[str] = [] @@ -204,10 +208,6 @@ To revalidate these numbers, run `ruff check --select=C901,PLR091`. """ - # TODO(sbc): Remove this import, or move it to the top, once we resolve the - # circular dependency issue with ports/__init__.py -> system_libs.py -> cmdline.py - from tools import ports - should_exit = False skip = False builtin_settings = set(settings.keys()) LEGACY_ARGS = {'--js-opts', '--llvm-opts', '--llvm-lto', '--memory-init-file'} @@ -265,7 +265,7 @@ def consume_arg_file(): name = consume_arg() if not os.path.isfile(name): - exit_with_error("'%s': file not found: '%s'" % (arg, name)) + exit_with_error(f"'{arg}': file not found: '{name}'") return name if arg in LEGACY_FLAGS: @@ -383,7 +383,7 @@ diagnostics.warning('deprecated', 'please replace -g4 with -gsource-map') settings.GENERATE_SOURCE_MAP = 1 elif debug_level > 4: - exit_with_error("unknown argument: '%s'", arg) + exit_with_error(f"unknown argument: '{arg}'") else: if debug_level.startswith('force_dwarf'): exit_with_error('gforce_dwarf was a temporary option and is no longer necessary (use -g)') @@ -460,23 +460,13 @@ # libraries) os.environ['EM_CACHE'] = config.CACHE elif check_flag('--clear-cache'): - logger.info('clearing cache as requested by --clear-cache: `%s`', cache.cachedir) - cache.erase() - shared.perform_sanity_checks() # this is a good time for a sanity check - should_exit = True + options.clear_cache = True elif check_flag('--clear-ports'): - logger.info('clearing ports and cache as requested by --clear-ports') - ports.clear() - cache.erase() - shared.perform_sanity_checks() # this is a good time for a sanity check - should_exit = True + options.clear_ports = True elif check_flag('--check'): - print(version_string(), file=sys.stderr) - shared.check_sanity(force=True) - should_exit = True + options.check = True elif check_flag('--show-ports'): - ports.show_ports() - should_exit = True + options.show_ports = True elif check_arg('--valid-abspath'): options.valid_abspaths.append(consume_arg()) elif arg.startswith(('-I', '-L')): @@ -587,7 +577,7 @@ if options.target not in {'wasm32', 'wasm64', 'wasm64-unknown-emscripten', 'wasm32-unknown-emscripten'}: exit_with_error(f'unsupported target: {options.target} (emcc only supports wasm64-unknown-emscripten and wasm32-unknown-emscripten)') elif check_arg('--use-port'): - ports.handle_use_port_arg(settings, consume_arg()) + options.use_ports.append(consume_arg()) elif arg in {'-c', '--precompile'}: options.dash_c = True elif arg == '-S': @@ -624,9 +614,6 @@ elif arg and (arg == '-' or not arg.startswith('-')): options.input_files.append(arg) - if should_exit: - sys.exit(0) - return [a for a in newargs if a] @@ -635,7 +622,7 @@ value = value.strip() match = re.match(r'^(\d+)\s*([kmgt]?b)?$', value, re.I) if not match: - exit_with_error("invalid byte size `%s`. Valid suffixes are: kb, mb, gb, tb" % value) + exit_with_error(f'invalid byte size `{value}`. Valid suffixes are: kb, mb, gb, tb') value, suffix = match.groups() value = int(value) if suffix: