diff --git a/ChangeLog.md b/ChangeLog.md index 3fd7a9404c27..5e80f7297afb 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -28,7 +28,10 @@ See docs/process.md for more on how version tagging works. support these older engines you can use these settings (`-sMIN_SAFARI_VERSION=120000` and/or `-sMIN_EDGE_VERSION=44`) to revert to the previous defaults, which will result in the new proposals being disabled. - (#17690) + Note that in order to avoid support for the sign-extension emscripten uses + a binaryen pass, so targeting older browsers requires the running of wasm-opt + and is therefore incompatible with `ERROR_ON_WASM_CHANGES_AFTER_LINK` (i.e. + fast linking). (#17690) - Added `--reproduce` command line flag (or equivalently `EMCC_REPRODUCE` environment variable). This options specifies the name of a tar file into which emscripten will copy all of the input files along with a response file diff --git a/emcc.py b/emcc.py index 3eb6f7e312f7..7374d40f25de 100755 --- a/emcc.py +++ b/emcc.py @@ -47,6 +47,7 @@ from tools.response_file import substitute_response_files from tools.minimal_runtime_shell import generate_minimal_runtime_html import tools.line_endings +from tools import feature_matrix from tools import js_manipulation from tools import wasm2c from tools import webassembly @@ -466,21 +467,6 @@ def apply_user_settings(): settings.LTO = 0 if value else 'full' -# apply minimum browser version defaults based on user settings. if -# a user requests a feature that we know is only supported in browsers -# from a specific version and above, we can assume that browser version. -def apply_min_browser_versions(): - - def default_min_browser_version(browser, version): - default_setting(f'MIN_{browser.upper()}_VERSION', version) - - if settings.WASM_BIGINT: - default_min_browser_version('Safari', 150000) - default_min_browser_version('Edge', 79) - default_min_browser_version('Firefox', 68) - # Chrome has BigInt since v67 which is less than default min version. - - def is_ar_file_with_missing_index(archive_file): # We parse the archive header outselves because llvm-nm --print-armap is slower and less # reliable. @@ -614,6 +600,11 @@ def get_binaryen_passes(): passes += ['--safe-heap'] if settings.MEMORY64 == 2: passes += ['--memory64-lowering'] + # sign-ext is enabled by default by llvm. If the target browser settings don't support + # this we lower it away here using a binaryen pass. + if not feature_matrix.caniuse(feature_matrix.Feature.SIGN_EXT): + logger.debug('lowering sign-ext feature due to incompatiable target browser engines') + passes += ['--signext-lowering'] if optimizing: passes += ['--post-emscripten'] if optimizing: @@ -2748,7 +2739,7 @@ def get_full_import_name(name): if settings.WASM_EXCEPTIONS: settings.EXPORTED_FUNCTIONS += ['___cpp_exception', '___cxa_increment_exception_refcount', '___cxa_decrement_exception_refcount', '___thrown_object_from_unwind_exception'] - apply_min_browser_versions() + feature_matrix.apply_min_browser_versions() if settings.SIDE_MODULE: # For side modules, we ignore all REQUIRED_EXPORTS that might have been added above. diff --git a/src/settings.js b/src/settings.js index 105baba1714d..647da3772e4c 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1968,7 +1968,9 @@ var SEPARATE_DWARF_URL = ''; // not in others like split-dwarf). // When this flag is turned on, we error at link time if the build requires any // changes to the wasm after link. This can be useful in testing, for example. -// [link] +// Some example of features that require post-link wasm changes are: +// - Lowering i64 to i32 pairs at the JS boundary (See WASM_BIGINT) +// - Lowering sign-extnesion operation when targeting older browsers. var ERROR_ON_WASM_CHANGES_AFTER_LINK = false; // Abort on unhandled excptions that occur when calling exported WebAssembly diff --git a/test/other/test_signext_lowering.c b/test/other/test_signext_lowering.c new file mode 100644 index 000000000000..e5b366b94139 --- /dev/null +++ b/test/other/test_signext_lowering.c @@ -0,0 +1,6 @@ +#include +#include + +int main(int argc, char* argv[]) { + printf("%lld\n", (int64_t)argc); +} diff --git a/test/test_other.py b/test/test_other.py index 59117c2165b9..72aeec601a0c 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -12724,3 +12724,32 @@ def test_reproduce(self): response = response.replace('\\', '/') response = response.replace(root, '') self.assertTextDataIdentical(expected, response) + + def test_signext_lowering(self): + cmd = [EMCC, test_file('other/test_signext_lowering.c'), '-sWASM_BIGINT'] + + # We expect ERROR_ON_WASM_CHANGES_AFTER_LINK to fail due lowering of sign-ext being + # required for safari 12. + output = self.expect_fail(cmd + ['-sERROR_ON_WASM_CHANGES_AFTER_LINK', '-sMIN_SAFARI_VERSION=120000']) + self.assertContained('error: changes to the wasm are required after link, but disallowed by ERROR_ON_WASM_CHANGES_AFTER_LINK', output) + self.assertContained('--signext-lowering', output) + + # Same for firefox + output = self.expect_fail(cmd + ['-sERROR_ON_WASM_CHANGES_AFTER_LINK', '-sMIN_FIREFOX_VERSION=61']) + self.assertContained('error: changes to the wasm are required after link, but disallowed by ERROR_ON_WASM_CHANGES_AFTER_LINK', output) + self.assertContained('--signext-lowering', output) + + # Same for chrome + output = self.expect_fail(cmd + ['-sERROR_ON_WASM_CHANGES_AFTER_LINK', '-sMIN_CHROME_VERSION=73']) + self.assertContained('error: changes to the wasm are required after link, but disallowed by ERROR_ON_WASM_CHANGES_AFTER_LINK', output) + self.assertContained('--signext-lowering', output) + + # Running the same command with safari 14.1 should succeed because no lowering + # is required. + self.run_process(cmd + ['-sERROR_ON_WASM_CHANGES_AFTER_LINK', '-sMIN_SAFARI_VERSION=140100']) + self.assertEqual('1\n', self.run_js('a.out.js')) + + # Running without ERROR_ON_WASM_CHANGES_AFTER_LINK but with safari 12 + # should successfully lower sign-ext. + self.run_process(cmd + ['-sMIN_SAFARI_VERSION=120000', '-o', 'lowered.js']) + self.assertEqual('1\n', self.run_js('lowered.js')) diff --git a/tools/feature_matrix.py b/tools/feature_matrix.py new file mode 100644 index 000000000000..769045fb78d6 --- /dev/null +++ b/tools/feature_matrix.py @@ -0,0 +1,88 @@ +# 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. + +"""Utilities for mapping browser versions to webassembly features.""" + +import logging +from enum import IntEnum, auto + +from .settings import settings, user_settings + +logger = logging.getLogger('feature_matrix') + + +class Feature(IntEnum): + NON_TRAPPING_FPTOINT = auto() + SIGN_EXT = auto() + BULK_MEMORY = auto() + MUTABLE_GLOBALS = auto() + JS_BIGINT_INTEGRATION = auto() + + +default_features = {Feature.SIGN_EXT, Feature.MUTABLE_GLOBALS} + +min_browser_versions = { + Feature.NON_TRAPPING_FPTOINT: { + 'chrome': 75, + 'firefox': 65, + 'safari': 150000, + }, + Feature.SIGN_EXT: { + 'chrome': 74, + 'firefox': 62, + 'safari': 140100, + }, + Feature.BULK_MEMORY: { + 'chrome': 75, + 'firefox': 79, + 'safari': 150000, + }, + Feature.MUTABLE_GLOBALS: { + 'chrome': 74, + 'firefox': 61, + 'safari': 120000, + }, + Feature.JS_BIGINT_INTEGRATION: { + 'chrome': 67, + 'firefox': 68, + 'safari': 150000, + }, +} + + +def caniuse(feature): + min_versions = min_browser_versions[feature] + if settings.MIN_CHROME_VERSION < min_versions['chrome']: + return False + # For edge we just use the same version requirements as chrome since, + # at least for modern versions of edge, they share version numbers. + if settings.MIN_EDGE_VERSION < min_versions['chrome']: + return False + if settings.MIN_FIREFOX_VERSION < min_versions['firefox']: + return False + if settings.MIN_SAFARI_VERSION < min_versions['safari']: + return False + # IE don't support any non-MVP features + if settings.MIN_IE_VERSION != 0x7FFFFFFF: + return False + return True + + +def enable_feature(feature): + """Updates default settings for browser versions such that the given + feature is available everywhere. + """ + for name, min_version in min_browser_versions[feature].items(): + name = f'MIN_{name.upper()}_VERSION' + if settings[name] < min_version and name not in user_settings: + setattr(settings, name, min_version) + + +# apply minimum browser version defaults based on user settings. if +# a user requests a feature that we know is only supported in browsers +# from a specific version and above, we can assume that browser version. +def apply_min_browser_versions(): + if settings.WASM_BIGINT: + enable_feature(Feature.JS_BIGINT_INTEGRATION)