diff --git a/emcc.py b/emcc.py index c26eefdfa30b3..92ea71e67fd91 100755 --- a/emcc.py +++ b/emcc.py @@ -50,6 +50,7 @@ from tools import wasm2c from tools import webassembly from tools import config +from tools import feature_matrix from tools.settings import settings, MEM_SIZE_SETTINGS, COMPILE_TIME_SETTINGS from tools.utils import read_file, write_file, read_binary @@ -829,7 +830,7 @@ def get_target_flags(): def get_clang_flags(user_args): - flags = get_target_flags() + flags = get_target_flags() + feature_matrix.get_feature_flags() # if exception catching is disabled, we can prevent that code from being # generated in the frontend diff --git a/test/test_other.py b/test/test_other.py index 6d614578ba46b..792e9c9f044bb 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -10612,7 +10612,8 @@ def fail(args, details): def test_output_to_nowhere(self): self.run_process([EMXX, test_file('hello_world.cpp'), '-o', os.devnull, '-c']) - # Test that passing -sMIN_X_VERSION=-1 on the command line will result in browser X being not supported at all. + # Test that passing -sMIN_X_VERSION=-1 on the command line will result in browser X being not + # supported at all. # I.e. -sMIN_X_VERSION=-1 is equal to -sMIN_X_VERSION=Infinity def test_drop_support_for_browser(self): # Test that -1 means "not supported" @@ -10620,6 +10621,29 @@ def test_drop_support_for_browser(self): self.assertContained('allowsDeferredCalls: true', read_file('a.out.js')) self.assertNotContained('allowsDeferredCalls: JSEvents.isInternetExplorer()', read_file('a.out.js')) + def test_browser_version_auto_features(self): + def get_enabled_features(filename): + with webassembly.Module(filename) as o: + features = o.parse_features_section() + return [x[1] for x in features if x[0] == '+'] + + # By default we expect to see some features disabled since our min browser versions + # don't allow them. + self.run_process([EMCC, '-mcpu=bleeding-edge', '-c', test_file('hello_world.c')]) + enabled = get_enabled_features('hello_world.o') + self.assertNotContained('nontrapping-fptoint', enabled) + self.assertNotContained('mutable-globals', enabled) + + # If we not disable all support for Edge/Safari/Firefox those feature should now be + # enabled by the -mcpu=bleeding-edge. + self.run_process([EMCC, '-mcpu=bleeding-edge', '-c', test_file('hello_world.c'), + '-sMIN_EDGE_VERSION=-1', + '-sMIN_SAFARI_VERSION=-1', + '-sMIN_FIREFOX_VERSION=-1']) + enabled = get_enabled_features('hello_world.o') + self.assertContained('nontrapping-fptoint', enabled) + self.assertContained('mutable-globals', enabled) + def test_errno_type(self): create_file('errno_type.c', ''' #include diff --git a/tools/feature_matrix.py b/tools/feature_matrix.py new file mode 100644 index 0000000000000..2f5cc63767df0 --- /dev/null +++ b/tools/feature_matrix.py @@ -0,0 +1,86 @@ +# 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 llvm feature flags.""" + +import logging +from enum import IntEnum, auto + +from .settings import settings + +logger = logging.getLogger('feature_matrix') + + +class Feature(IntEnum): + NON_TRAPPING_FPTOINT = auto() + SIGN_EXT = auto() + BULK_MEMORY = auto() + MUTABLE_GLOBALS = auto() + + +min_browser_versions = { + Feature.NON_TRAPPING_FPTOINT: { + 'chrome': 75, + 'firefox': 65, + 'safari': 150000, + }, + Feature.SIGN_EXT: { + 'chrome': 74, + 'firefox': 62, + 'safari': 141000, + }, + Feature.BULK_MEMORY: { + 'chrome': 75, + 'firefox': 79, + 'safari': 150000, + }, + Feature.MUTABLE_GLOBALS: { + 'chrome': 74, + 'firefox': -1, # Firefox has always supported mutable globals apparently + 'safari': 120000, + }, +} + + +def caniuse(feature): + min_versions = min_browser_versions[feature] + if settings.MIN_CHROME_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 and Edge don't support any non-MVP features + if settings.MIN_IE_VERSION != 0x7FFFFFFF or settings.MIN_EDGE_VERSION != 0x7FFFFFFF: + return False + return True + + +def get_feature_flags(): + flags = [] + + # Enabling threads, or PIC implicitly opts into many of these features. + # TODO(sbc): We should probably have USE_PTHREADS/RELOCATABLE + # implicitly bump the min browser versions instead. + if settings.USE_PTHREADS or settings.RELOCATABLE: + return flags + + if not caniuse(Feature.NON_TRAPPING_FPTOINT): + logger.debug('adding -mno-nontrapping-fptoint due to target browser selection') + flags.append('-mno-nontrapping-fptoint') + + if not caniuse(Feature.SIGN_EXT): + logger.debug('adding -mnosign-ext due to target browser selection') + flags.append('-mno-sign-ext') + + if not caniuse(Feature.BULK_MEMORY): + logger.debug('adding -mnobulk-memory due to target browser selection') + flags.append('-mno-bulk-memory') + + if not caniuse(Feature.MUTABLE_GLOBALS): + logger.debug('adding -mnomutable-globals due to target browser selection') + flags.append('-mno-mutable-globals') + + return flags diff --git a/tools/settings.py b/tools/settings.py index 9c8480811161f..0f18c88a94d24 100644 --- a/tools/settings.py +++ b/tools/settings.py @@ -69,6 +69,13 @@ 'WASM_OBJECT_FILES', 'WASM_WORKERS', + # These are needed at compile time to calulate feature flags + 'MIN_CHROME_VERSION', + 'MIN_FIREFOX_VERSION', + 'MIN_SAFARI_VERSION', + 'MIN_IE_VERSION', + 'MIN_EDGE_VERSION', + # Internal settings used during compilation 'EXCEPTION_CATCHING_ALLOWED', 'WASM_EXCEPTIONS',