blob: 38a908eaa4b0cf6b6daf03d3a748793fbce734fe [file]
# 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 . import diagnostics
from .settings import settings, user_settings
logger = logging.getLogger('feature_matrix')
UNSUPPORTED = 0x7FFFFFFF
# Oldest support browser versions.
# Emscripten unconditionally requires support for:
# - DedicatedWorkerGlobalScope.name parameter for multithreading support, which
# landed first in Chrome 70, Firefox 55 and Safari 12.2.
# N.b. when modifying these values, update comments in src/settings.js on
# MIN_x_VERSION fields to match accordingly.
OLDEST_SUPPORTED_CHROME = 85 # Released on 2020-08-25
OLDEST_SUPPORTED_FIREFOX = 79 # Released on 2020-07-28
OLDEST_SUPPORTED_SAFARI = 140100 # Released on 2021-04-26
# This is the oldest version of node that we do any testing with.
# Keep this in sync with the test-node-compat in .circleci/config.yml.
OLDEST_SUPPORTED_NODE = 180300
class Feature(IntEnum):
NON_TRAPPING_FPTOINT = auto()
BULK_MEMORY = auto()
JS_BIGINT_INTEGRATION = auto()
MEMORY64 = auto()
WORKER_ES6_MODULES = auto()
OFFSCREENCANVAS_SUPPORT = auto()
WASM_LEGACY_EXCEPTIONS = auto()
WASM_EXCEPTIONS = auto()
WEBGL2 = auto()
WEBGPU = auto()
GROWABLE_ARRAYBUFFERS = auto()
disable_override_features = set()
enable_override_features = set()
min_browser_versions = {
Feature.NON_TRAPPING_FPTOINT: {
'chrome': 75,
'firefox': 65,
'safari': 150000,
'node': 130000,
},
Feature.BULK_MEMORY: {
'chrome': 75,
'firefox': 79,
'safari': 150000,
'node': 130000,
},
Feature.JS_BIGINT_INTEGRATION: {
'chrome': 67,
'firefox': 78,
'safari': 150000,
'node': 130000,
},
Feature.MEMORY64: {
'chrome': 128,
'firefox': 129,
'safari': UNSUPPORTED,
'node': 230000,
},
# Emscripten itself does not use this feature but we use it in our browser
# tests.
Feature.WEBGL2: {
'chrome': 56,
'firefox': 51,
'safari': 150000,
'node': UNSUPPORTED,
},
# Emscripten itself does not use this feature but we use it in our browser
# tests.
Feature.WEBGPU: {
'chrome': 113,
'firefox': 141,
'safari': 260000,
'node': UNSUPPORTED,
},
# Emscripten itself does not use this feature but we use it in our browser
# tests.
# https://caniuse.com/mdn-api_worker_worker_ecmascript_modules: The ability to
# call new Worker(url, { type: 'module' });
Feature.WORKER_ES6_MODULES: {
'chrome': 80,
'firefox': 114,
'safari': 150000,
'node': 0, # This is a browser only feature, no requirements on Node.js
},
# OffscreenCanvas feature allows creating canvases that are not connected to
# a visible DOM element, e.g. in a Worker.
# https://caniuse.com/offscreencanvas
Feature.OFFSCREENCANVAS_SUPPORT: {
'chrome': 69,
'firefox': 105,
'safari': 170000,
'node': 0, # This is a browser only feature, no requirements on Node.js
},
# Legacy Wasm exceptions was the first (now legacy) format for native
# exception handling in WebAssembly.
Feature.WASM_LEGACY_EXCEPTIONS: {
'chrome': 95,
'firefox': 100,
'safari': 150200,
'node': 170000,
},
# Wasm exceptions is a newer format for native exception handling in
# WebAssembly.
Feature.WASM_EXCEPTIONS: {
'chrome': 137,
'firefox': 131,
'safari': 180400,
# Supported with flag --experimental-wasm-exnref (TODO: Change this to
# unflagged version of Node.js 260000 that ships Wasm EH enabled, after
# Emscripten unit testing has migrated to Node.js 26, and Emsdk ships
# Node.js 26)
'node': 220000,
},
# Growable SharedArrayBuffers improves memory growth feature in multithreaded
# builds by avoiding need to poll resizes to ArrayBuffer views in Workers.
# This feature is not used anywhere else except the test harness to detect
# browser version.
Feature.GROWABLE_ARRAYBUFFERS: {
'chrome': 136,
'firefox': 145,
'safari': UNSUPPORTED,
'node': 240000,
},
# The following features we now support unconditionally, but keeping them around
# in code comments for future reference
# Feature.MUTABLE_GLOBALS: {
# 'chrome': 74,
# 'firefox': 61,
# 'safari': 130100,
# 'node': 120000,
# },
# Feature.SIGN_EXT: {
# 'chrome': 74,
# 'firefox': 62,
# 'safari': 140100,
# 'node': 120000,
# },
# Feature.THREADS: {
# 'chrome': 74,
# 'firefox': 79,
# 'safari': 140100,
# 'node': 160400,
# },
# Feature.PROMISE_ANY: {
# 'chrome': 85,
# 'firefox': 79,
# 'safari': 140000,
# 'node': 150000,
# },
}
# Static assertion to check that we actually need each of the above feature flags
# Once the OLDEST_SUPPORTED_XX versions are high enough they can/should be removed.
for feature, reqs in min_browser_versions.items():
always_present = (reqs['chrome'] <= OLDEST_SUPPORTED_CHROME and
reqs['firefox'] <= OLDEST_SUPPORTED_FIREFOX and
reqs['safari'] <= OLDEST_SUPPORTED_SAFARI and
reqs['node'] <= OLDEST_SUPPORTED_NODE)
assert not always_present, f'{feature.name} is no longer needed'
def caniuse(feature):
if feature in disable_override_features:
return False
if feature in enable_override_features:
return True
min_versions = min_browser_versions[feature]
def report_missing(setting_name):
setting_value = getattr(settings, setting_name)
logger.debug(f'cannot use {feature.name} because {setting_name} is too old: {setting_value}')
if settings.MIN_CHROME_VERSION < min_versions['chrome']:
report_missing('MIN_CHROME_VERSION')
return False
if settings.MIN_FIREFOX_VERSION < min_versions['firefox']:
report_missing('MIN_FIREFOX_VERSION')
return False
if settings.MIN_SAFARI_VERSION < min_versions['safari']:
report_missing('MIN_SAFARI_VERSION')
return False
if 'node' in min_versions and settings.MIN_NODE_VERSION < min_versions['node']:
report_missing('MIN_NODE_VERSION')
return False
return True
def enable_feature(feature, reason, override=False):
"""Update default settings for browser versions such that the given feature is available everywhere."""
if override:
enable_override_features.add(feature)
for name, min_version in min_browser_versions[feature].items():
name = f'MIN_{name.upper()}_VERSION'
if settings[name] < min_version:
if name in user_settings:
# If the user explicitly chose an older version we issue a warning.
if name == 'MIN_SAFARI_VERSION' and reason == 'pthreads':
# But as a special case, don't warn when forcing on bulk memory on Safari.
# This is because Safari implemented part of bulk memory along with threads in 14.1,
# but not all of it. So bulk-mem is listed as supported in 15.0. So we want to
# continue enabling bulk memory via pthreads without a warning in 14.1, but without
# enabling other features requiring 15.0.
continue
diagnostics.warning(
'compatibility',
f'{name}={user_settings[name]} is not compatible with {reason} '
f'({name}={min_version} or above required)')
else:
# If no conflict, bump the minimum version to accommodate the feature.
logger.debug(f'Enabling {name}={min_version} to accommodate {reason}')
setattr(settings, name, min_version)
def disable_feature(feature):
"""Allow the user to disable a feature that would otherwise be on by default."""
disable_override_features.add(feature)
# 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 and 'WASM_BIGINT' in user_settings:
# WASM_BIGINT is enabled by default, don't use it to enable other features
# unless the user explicitly enabled it.
enable_feature(Feature.JS_BIGINT_INTEGRATION, 'WASM_BIGINT')
if settings.PTHREADS:
enable_feature(Feature.BULK_MEMORY, 'pthreads')
elif settings.WASM_WORKERS or settings.SHARED_MEMORY:
enable_feature(Feature.BULK_MEMORY, 'shared-mem')
if settings.MEMORY64 == 1:
enable_feature(Feature.MEMORY64, 'MEMORY64')
if settings.EXPORT_ES6 and settings.PTHREADS:
enable_feature(Feature.WORKER_ES6_MODULES, 'EXPORT_ES6 with -pthread')
if settings.EXPORT_ES6 and settings.WASM_WORKERS:
enable_feature(Feature.WORKER_ES6_MODULES, 'EXPORT_ES6 with -sWASM_WORKERS')
if settings.OFFSCREENCANVAS_SUPPORT:
enable_feature(Feature.OFFSCREENCANVAS_SUPPORT, 'OFFSCREENCANVAS_SUPPORT')
if settings.WASM_EXCEPTIONS or settings.SUPPORT_LONGJMP == 'wasm': # Wasm longjmp support will lean on Wasm (Legacy) EH
if settings.WASM_LEGACY_EXCEPTIONS:
enable_feature(Feature.WASM_LEGACY_EXCEPTIONS, 'Wasm Legacy exceptions (-fwasm-exceptions with -sWASM_LEGACY_EXCEPTIONS=1)')
else:
enable_feature(Feature.WASM_EXCEPTIONS, 'Wasm exceptions (-fwasm-exceptions with -sWASM_LEGACY_EXCEPTIONS=0)')